Beispiel #1
0
class ProductTemplate(models.Model):
    _inherit = "product.template"

    quotation_only_description = fields.Html(
        'Quotation Only Description',
        sanitize_attributes=False,
        translate=html_translate,
        help="The quotation description (not used on eCommerce)")

    quotation_description = fields.Html(
        'Quotation Description',
        compute='_compute_quotation_description',
        help=
        "This field uses the Quotation Only Description if it is defined, otherwise it will try to read the eCommerce Description."
    )

    @api.multi
    def _compute_quotation_description(self):
        for record in self:
            if record.quotation_only_description:
                record.quotation_description = record.quotation_only_description
            elif hasattr(record,
                         'website_description') and record.website_description:
                record.quotation_description = record.website_description
            else:
                record.quotation_description = ''
class KarmaRank(models.Model):
    _name = 'gamification.karma.rank'
    _description = 'Rank based on karma'
    _inherit = 'image.mixin'
    _order = 'karma_min'

    name = fields.Text(string='Rank Name', translate=True, required=True)
    description = fields.Html(
        string='Description',
        translate=html_translate,
        sanitize_attributes=False,
    )
    description_motivational = fields.Html(
        string='Motivational',
        translate=html_translate,
        sanitize_attributes=False,
        help="Motivational phrase to reach this rank")
    karma_min = fields.Integer(string='Required Karma',
                               help='Minimum karma needed to reach this rank')
    user_ids = fields.One2many('res.users',
                               'rank_id',
                               string='Users',
                               help="Users having this rank")

    @api.model_create_multi
    def create(self, values_list):
        res = super(KarmaRank, self).create(values_list)
        users = self.env['res.users'].sudo().search([('karma', '>', 0)])
        users._recompute_rank()
        return res

    def write(self, vals):
        if 'karma_min' in vals:
            previous_ranks = self.env['gamification.karma.rank'].search(
                [], order="karma_min DESC").ids
            low = min(vals['karma_min'], self.karma_min)
            high = max(vals['karma_min'], self.karma_min)

        res = super(KarmaRank, self).write(vals)

        if 'karma_min' in vals:
            after_ranks = self.env['gamification.karma.rank'].search(
                [], order="karma_min DESC").ids
            if previous_ranks != after_ranks:
                users = self.env['res.users'].sudo().search([('karma', '>', 0)
                                                             ])
            else:
                users = self.env['res.users'].sudo().search([
                    ('karma', '>=', low), ('karma', '<=', high)
                ])
            users._recompute_rank()
        return res
Beispiel #3
0
class User(models.Model):

    _inherit = ["res.users", "website_dependent.mixin"]
    _name = "res.users"

    signature = fields.Html(company_dependent=True, website_dependent=True)

    # extra field to detach email field from res.partner
    email = fields.Char(string="Multi Website Email",
                        related="email_multi_website",
                        inherited=False)
    email_multi_website = fields.Char(company_dependent=True,
                                      website_dependent=True)

    @api.model
    def create(self, vals):
        res = super(User, self).create(vals)
        # make value company independent
        res._force_default(FIELD_NAME, vals.get("email"))
        for f in FIELDS:
            res._force_default(f, vals.get(f))
        return res

    def write(self, vals):
        res = super(User, self).write(vals)
        # TODO: will it work with OCA's partner_firstname module?
        if any(k in vals for k in ["name", "email"] + FIELDS):
            for f in ALL_FIELDS:
                self._update_properties_label(f)
        return res

    def _auto_init(self):
        for f in FIELDS:
            self._auto_init_website_dependent(f)
        return super(User, self)._auto_init()
class Job(models.Model):

    _name = 'hr.job'
    _inherit = [
        'hr.job', 'website.seo.metadata', 'website.published.multi.mixin'
    ]

    def _get_default_website_description(self):
        default_description = self.env["ir.model.data"].xmlid_to_object(
            "website_hr_recruitment.default_website_description")
        return (default_description.render() if default_description else "")

    website_description = fields.Html('Website description',
                                      translate=html_translate,
                                      sanitize_attributes=False,
                                      default=_get_default_website_description,
                                      prefetch=False)

    def _compute_website_url(self):
        super(Job, self)._compute_website_url()
        for job in self:
            job.website_url = "/jobs/detail/%s" % job.id

    def set_open(self):
        self.write({'website_published': False})
        return super(Job, self).set_open()
Beispiel #5
0
class SaleOrder(models.Model):
    _inherit = 'sale.order'

    website_description = fields.Html('Website Description', sanitize_attributes=False, translate=html_translate)

    @api.onchange('partner_id')
    def onchange_update_description_lang(self):
        if not self.sale_order_template_id:
            return
        else:
            template = self.sale_order_template_id.with_context(lang=self.partner_id.lang)
            self.website_description = template.website_description

    def _compute_line_data_for_template_change(self, line):
        vals = super(SaleOrder, self)._compute_line_data_for_template_change(line)
        vals.update(website_description=line.website_description)
        return vals

    def _compute_option_data_for_template_change(self, option):
        vals = super(SaleOrder, self)._compute_option_data_for_template_change(option)
        vals.update(website_description=option.website_description)
        return vals

    @api.onchange('sale_order_template_id')
    def onchange_sale_order_template_id(self):
        ret = super(SaleOrder, self).onchange_sale_order_template_id()
        if self.sale_order_template_id:
            template = self.sale_order_template_id.with_context(lang=self.partner_id.lang)
            self.website_description = template.website_description
        return ret
Beispiel #6
0
class ConverterTest(models.Model):
    _name = 'web_editor.converter.test'
    _description = 'Web Editor Converter Test'

    # disable translation export for those brilliant field labels and values
    _translate = False

    char = fields.Char()
    integer = fields.Integer()
    float = fields.Float()
    numeric = fields.Float(digits=(16, 2))
    many2one = fields.Many2one('web_editor.converter.test.sub')
    binary = fields.Binary()
    date = fields.Date()
    datetime = fields.Datetime()
    selection = fields.Selection([
        (1, "réponse A"),
        (2, "réponse B"),
        (3, "réponse C"),
        (4, "réponse <D>"),
    ])
    selection_str = fields.Selection(
        [
            ('A', "Qu'il n'est pas arrivé à Toronto"),
            ('B', "Qu'il était supposé arriver à Toronto"),
            ('C', "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?"),
            ('D', "La réponse D"),
        ],
        string=u"Lorsqu'un pancake prend l'avion à destination de Toronto et "
        u"qu'il fait une escale technique à St Claude, on dit:")
    html = fields.Html()
    text = fields.Text()
class SaleOrderTemplateLine(models.Model):
    _inherit = "sale.order.template.line"

    website_description = fields.Html(
        'Website Description',
        related='product_id.product_tmpl_id.quotation_only_description',
        translate=html_translate,
        readonly=False)

    @api.onchange('product_id')
    def _onchange_product_id(self):
        ret = super(SaleOrderTemplateLine, self)._onchange_product_id()
        if self.product_id:
            self.website_description = self.product_id.quotation_description
        return ret

    @api.model
    def create(self, values):
        values = self._inject_quotation_description(values)
        return super(SaleOrderTemplateLine, self).create(values)

    def write(self, values):
        values = self._inject_quotation_description(values)
        return super(SaleOrderTemplateLine, self).write(values)

    def _inject_quotation_description(self, values):
        values = dict(values or {})
        if not values.get('website_description') and values.get('product_id'):
            product = self.env['product.product'].browse(values['product_id'])
            values['website_description'] = product.quotation_description
        return values
Beispiel #8
0
class test_model(models.Model):
    _name = 'test_converter.test_model'
    _description = 'Test Converter Model'

    char = fields.Char()
    integer = fields.Integer()
    float = fields.Float()
    numeric = fields.Float(digits=(16, 2))
    many2one = fields.Many2one('test_converter.test_model.sub',
                               group_expand='_gbf_m2o')
    binary = fields.Binary(attachment=False)
    date = fields.Date()
    datetime = fields.Datetime()
    selection_str = fields.Selection(
        [
            ('A', u"Qu'il n'est pas arrivé à Toronto"),
            ('B', u"Qu'il était supposé arriver à Toronto"),
            ('C', u"Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?"),
            ('D', u"La réponse D"),
        ],
        string=u"Lorsqu'un pancake prend l'avion à destination de Toronto et "
        u"qu'il fait une escale technique à St Claude, on dit:")
    html = fields.Html()
    text = fields.Text()

    # `base` module does not contains any model that implement the functionality
    # `group_expand`; test this feature here...

    @api.model
    def _gbf_m2o(self, subs, domain, order):
        sub_ids = subs._search([], order=order, access_rights_uid=SUPERUSER_ID)
        return subs.browse(sub_ids)
Beispiel #9
0
class MailingList(models.Model):
    _inherit = 'mailing.list'

    def _default_toast_content(self):
        return '<p>Thanks for subscribing!</p>'

    website_popup_ids = fields.One2many('website.mass_mailing.popup',
                                        'mailing_list_id',
                                        string="Website Popups")
    toast_content = fields.Html(default=_default_toast_content, translate=True)
class MassMailingPopup(models.Model):
    _name = 'website.mass_mailing.popup'
    _description = "Mailing list popup"

    def _default_popup_content(self):
        return self.env['ir.ui.view'].render_template('website_mass_mailing.s_newsletter_block')

    mailing_list_id = fields.Many2one('mailing.list')
    website_id = fields.Many2one('website')
    popup_content = fields.Html(string="Website Popup Content", default=_default_popup_content, translate=True, sanitize=False)
Beispiel #11
0
class SaleOrderTemplateOption(models.Model):
    _inherit = "sale.order.template.option"

    website_description = fields.Html('Website Description', translate=html_translate, sanitize_attributes=False)

    @api.onchange('product_id')
    def _onchange_product_id(self):
        ret = super(SaleOrderTemplateOption, self)._onchange_product_id()
        if self.product_id:
            self.website_description = self.product_id.quotation_description
        return ret
Beispiel #12
0
class ProductPublicCategory(models.Model):
    _name = "product.public.category"
    _inherit = ["website.seo.metadata", "website.multi.mixin", 'image.mixin']
    _description = "Website Product Category"
    _parent_store = True
    _order = "sequence, name"

    name = fields.Char(required=True, translate=True)
    parent_id = fields.Many2one('product.public.category',
                                string='Parent Category',
                                index=True)
    parent_path = fields.Char(index=True)
    child_id = fields.One2many('product.public.category',
                               'parent_id',
                               string='Children Categories')
    parents_and_self = fields.Many2many('product.public.category',
                                        compute='_compute_parents_and_self')
    sequence = fields.Integer(
        help=
        "Gives the sequence order when displaying a list of product categories.",
        index=True)
    website_description = fields.Html('Category Description',
                                      sanitize_attributes=False,
                                      translate=html_translate)
    product_tmpl_ids = fields.Many2many(
        'product.template',
        relation='product_public_category_product_template_rel')

    @api.constrains('parent_id')
    def check_parent_id(self):
        if not self._check_recursion():
            raise ValueError(
                _('Error ! You cannot create recursive categories.'))

    def name_get(self):
        res = []
        for category in self:
            res.append((category.id,
                        " / ".join(category.parents_and_self.mapped('name'))))
        return res

    def unlink(self):
        self.child_id.parent_id = None
        return super(ProductPublicCategory, self).unlink()

    def _compute_parents_and_self(self):
        for category in self:
            if category.parent_path:
                category.parents_and_self = self.env[
                    'product.public.category'].browse(
                        [int(p) for p in category.parent_path.split('/')[:-1]])
            else:
                category.parents_and_self = category
Beispiel #13
0
class Ks_NewSnippet(models.Model):
    _name = 'theme.ks_new_snippet'
    _description = 'Save the custom snippets'

    name = fields.Char("Name", required='true')
    ks_snippet_body = fields.Html('Snippet Html', sanitize=False)
    ks_snippet_css = fields.Text('Snippet Css')
    ks_snippet_thumbnail = fields.Binary("Thumbnail", attachment=True)

    @api.model
    def create(self, values):
        rec = super(Ks_NewSnippet, self).create(values)
        return rec
Beispiel #14
0
class SaleOrderTemplate(models.Model):
    _inherit = "sale.order.template"

    website_description = fields.Html('Website Description', translate=html_translate, sanitize_attributes=False)

    @api.multi
    def open_template(self):
        self.ensure_one()
        return {
            'type': 'ir.actions.act_url',
            'target': 'self',
            'url': '/sale_quotation_builder/template/%d' % self.id
        }
Beispiel #15
0
class WebsiteResPartner(models.Model):
    _name = 'res.partner'
    _inherit = ['res.partner', 'website.seo.metadata']

    website_description = fields.Html('Website Partner Full Description',
                                      strip_style=True,
                                      translate=html_translate)
    website_short_description = fields.Text(
        'Website Partner Short Description')

    def _compute_website_url(self):
        super(WebsiteResPartner, self)._compute_website_url()
        for partner in self:
            partner.website_url = "/partners/%s" % slug(partner)
Beispiel #16
0
class MixedModel(models.Model):
    _name = 'test_new_api.mixed'
    _description = 'Test New API Mixed'

    number = fields.Float(digits=(10, 2), default=3.14)
    number2 = fields.Float(digits='New API Precision')
    date = fields.Date()
    moment = fields.Datetime()
    now = fields.Datetime(compute='_compute_now')
    lang = fields.Selection(string='Language', selection='_get_lang')
    reference = fields.Reference(string='Related Document',
                                 selection='_reference_models')
    comment1 = fields.Html(sanitize=False)
    comment2 = fields.Html(sanitize_attributes=True, strip_classes=False)
    comment3 = fields.Html(sanitize_attributes=True, strip_classes=True)
    comment4 = fields.Html(sanitize_attributes=True, strip_style=True)

    currency_id = fields.Many2one(
        'res.currency', default=lambda self: self.env.ref('base.EUR'))
    amount = fields.Monetary()

    def _compute_now(self):
        # this is a non-stored computed field without dependencies
        for message in self:
            message.now = fields.Datetime.now()

    @api.model
    def _get_lang(self):
        return self.env['res.lang'].get_installed()

    @api.model
    def _reference_models(self):
        models = self.env['ir.model'].sudo().search([('state', '!=', 'manual')
                                                     ])
        return [(model.model, model.name) for model in models
                if not model.model.startswith('ir.')]
Beispiel #17
0
class ImLivechatChannel(models.Model):

    _name = 'im_livechat.channel'
    _inherit = ['im_livechat.channel', 'website.published.mixin']

    def _compute_website_url(self):
        super(ImLivechatChannel, self)._compute_website_url()
        for channel in self:
            channel.website_url = "/livechat/channel/%s" % (slug(channel), )

    website_description = fields.Html(
        "Website description",
        default=False,
        help="Description of the channel displayed on the website page",
        sanitize_attributes=False,
        translate=html_translate)
Beispiel #18
0
class DigestTip(models.Model):
    _name = 'digest.tip'
    _description = 'Digest Tips'
    _order = 'sequence'

    sequence = fields.Integer(
        'Sequence',
        default=1,
        help='Used to display digest tip in email template base on order')
    user_ids = fields.Many2many('res.users',
                                string='Recipients',
                                help='Users having already received this tip')
    tip_description = fields.Html('Tip description', translate=html_translate)
    group_id = fields.Many2one(
        'res.groups',
        string='Authorized Group',
        default=lambda self: self.env.ref('base.group_user'))
Beispiel #19
0
class SaleOrderOption(models.Model):
    _inherit = "sale.order.option"

    website_description = fields.Html('Website Description', sanitize_attributes=False, translate=html_translate)

    @api.onchange('product_id', 'uom_id')
    def _onchange_product_id(self):
        ret = super(SaleOrderOption, self)._onchange_product_id()
        if self.product_id:
            product = self.product_id.with_context(lang=self.order_id.partner_id.lang)
            self.website_description = product.quotation_description
        return ret

    def _get_values_to_add_to_order(self):
        values = super(SaleOrderOption, self)._get_values_to_add_to_order()
        values.update(website_description=self.website_description)
        return values
Beispiel #20
0
class website_menu(models.Model):
    _inherit = "website.menu"

    html_menu = fields.Html('Menu Design Block', translate=html_translate)
    is_dynamic_menu = fields.Boolean("Is Dynamic Menu", default=False)
    is_vertical_menu = fields.Boolean("Vertical menu", default=False)

    # method whcih redirect to frontend for design menu html structure
    def action_edit_menu(self, context=None):
        if not len(self.ids) == 1:
            raise ValueError('One and only one ID allowed for this action')

        url = '/menu_html_builder?model=website.menu&id=%d&enable_editor=1' % (
            self.id)
        return {
            'name': ('Edit Template'),
            'type': 'ir.actions.act_url',
            'url': url,
            'target': 'self',
        }

    @api.onchange('is_vertical_menu')
    def is_vertical_menu_change(self):
        res = self.env['website.menu'].sudo().search([
            ('is_vertical_menu', '=', True),
            ('website_id', 'in', (False, self.website_id.id))
        ])
        if res:
            if self.is_vertical_menu:
                res.write({'is_vertical_menu': False})
                self.is_vertical_menu = True
            else:
                self.is_vertical_menu = False

    @api.one
    def _compute_visible(self):
        visible = True
        if self.page_id and not self.page_id.sudo(
        ).is_visible and not self.user_has_groups('base.group_user'):
            visible = False
        if self.is_vertical_menu:
            visible = False
        self.is_visible = visible
Beispiel #21
0
class MassMailingList(models.Model):
    _inherit = 'mail.mass_mailing.list'

    def _default_popup_content(self):
        return """<div class="modal-header text-center">
    <h3 class="modal-title mt8">Eagle Presents</h3>
</div>
<div class="o_popup_message">
    <font>7</font>
    <strong>Business Hacks</strong>
    <span> to<br/>boost your marketing</span>
</div>
<p class="o_message_paragraph">Join our Marketing newsletter and get <strong>this white paper instantly</strong></p>"""

    popup_content = fields.Html(string="Website Popup Content",
                                translate=True,
                                sanitize_attributes=False,
                                default=_default_popup_content)
    popup_redirect_url = fields.Char(string="Website Popup Redirect URL",
                                     default='/')
Beispiel #22
0
class SaleOrderLine(models.Model):
    _inherit = "sale.order.line"

    website_description = fields.Html('Website Description', sanitize=False, translate=html_translate)

    @api.model
    def create(self, values):
        values = self._inject_quotation_description(values)
        return super(SaleOrderLine, self).create(values)

    def write(self, values):
        values = self._inject_quotation_description(values)
        return super(SaleOrderLine, self).write(values)

    def _inject_quotation_description(self, values):
        values = dict(values or {})
        if not values.get('website_description') and values.get('product_id'):
            product = self.env['product.product'].browse(values['product_id'])
            values.update(website_description=product.quotation_description)
        return values
Beispiel #23
0
class Track(models.Model):
    _name = "event.track"
    _description = 'Event Track'
    _order = 'priority, date'
    _inherit = [
        'mail.thread', 'mail.activity.mixin', 'website.seo.metadata',
        'website.published.mixin'
    ]

    @api.model
    def _get_default_stage_id(self):
        return self.env['event.track.stage'].search([], limit=1).id

    name = fields.Char('Title', required=True, translate=True)
    active = fields.Boolean(default=True)
    user_id = fields.Many2one('res.users',
                              'Responsible',
                              tracking=True,
                              default=lambda self: self.env.user)
    company_id = fields.Many2one('res.company', related='event_id.company_id')
    partner_id = fields.Many2one('res.partner', 'Speaker')
    partner_name = fields.Char('Name')
    partner_email = fields.Char('Email')
    partner_phone = fields.Char('Phone')
    partner_biography = fields.Html('Biography')
    tag_ids = fields.Many2many('event.track.tag', string='Tags')
    stage_id = fields.Many2one('event.track.stage',
                               string='Stage',
                               ondelete='restrict',
                               index=True,
                               copy=False,
                               default=_get_default_stage_id,
                               group_expand='_read_group_stage_ids',
                               required=True,
                               tracking=True)
    kanban_state = fields.Selection(
        [('normal', 'Grey'), ('done', 'Green'), ('blocked', 'Red')],
        string='Kanban State',
        copy=False,
        default='normal',
        required=True,
        tracking=True,
        help=
        "A track's kanban state indicates special situations affecting it:\n"
        " * Grey is the default situation\n"
        " * Red indicates something is preventing the progress of this track\n"
        " * Green indicates the track is ready to be pulled to the next stage")
    description = fields.Html(translate=html_translate,
                              sanitize_attributes=False)
    date = fields.Datetime('Track Date')
    date_end = fields.Datetime('Track End Date',
                               compute='_compute_end_date',
                               store=True)
    duration = fields.Float('Duration',
                            default=1.5,
                            help="Track duration in hours.")
    location_id = fields.Many2one('event.track.location', 'Room')
    event_id = fields.Many2one('event.event', 'Event', required=True)
    color = fields.Integer('Color Index')
    priority = fields.Selection([('0', 'Low'), ('1', 'Medium'), ('2', 'High'),
                                 ('3', 'Highest')],
                                'Priority',
                                required=True,
                                default='1')
    image = fields.Image("Image",
                         related='partner_id.image_128',
                         store=True,
                         readonly=False)

    @api.depends('name')
    def _compute_website_url(self):
        super(Track, self)._compute_website_url()
        for track in self:
            if track.id:
                track.website_url = '/event/%s/track/%s' % (slug(
                    track.event_id), slug(track))

    @api.onchange('partner_id')
    def _onchange_partner_id(self):
        if self.partner_id:
            self.partner_name = self.partner_id.name
            self.partner_email = self.partner_id.email
            self.partner_phone = self.partner_id.phone
            self.partner_biography = self.partner_id.website_description

    @api.depends('date', 'duration')
    def _compute_end_date(self):
        for track in self:
            if track.date:
                delta = timedelta(minutes=60 * track.duration)
                track.date_end = track.date + delta
            else:
                track.date_end = False

    @api.model
    def create(self, vals):
        track = super(Track, self).create(vals)

        track.event_id.message_post_with_view(
            'website_event_track.event_track_template_new',
            values={'track': track},
            subject=track.name,
            subtype_id=self.env.ref('website_event_track.mt_event_track').id,
        )

        return track

    def write(self, vals):
        if 'stage_id' in vals and 'kanban_state' not in vals:
            vals['kanban_state'] = 'normal'
        res = super(Track, self).write(vals)
        if vals.get('partner_id'):
            self.message_subscribe([vals['partner_id']])
        return res

    @api.model
    def _read_group_stage_ids(self, stages, domain, order):
        """ Always display all stages """
        return stages.search([], order=order)

    def _track_template(self, changes):
        res = super(Track, self)._track_template(changes)
        track = self[0]
        if 'stage_id' in changes and track.stage_id.mail_template_id:
            res['stage_id'] = (track.stage_id.mail_template_id, {
                'composition_mode':
                'comment',
                'auto_delete_message':
                True,
                'subtype_id':
                self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
                'email_layout_xmlid':
                'mail.mail_notification_light'
            })
        return res

    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'kanban_state' in init_values and self.kanban_state == 'blocked':
            return self.env.ref('website_event_track.mt_track_blocked')
        elif 'kanban_state' in init_values and self.kanban_state == 'done':
            return self.env.ref('website_event_track.mt_track_ready')
        return super(Track, self)._track_subtype(init_values)

    def _message_get_suggested_recipients(self):
        recipients = super(Track, self)._message_get_suggested_recipients()
        for track in self:
            if track.partner_email and track.partner_email != track.partner_id.email:
                track._message_add_suggested_recipient(
                    recipients,
                    email=track.partner_email,
                    reason=_('Speaker Email'))
        return recipients

    def _message_post_after_hook(self, message, msg_vals):
        if self.partner_email and not self.partner_id:
            # we consider that posting a message with a specified recipient (not a follower, a specific one)
            # on a document without customer means that it was created through the chatter using
            # suggested recipients. This heuristic allows to avoid ugly hacks in JS.
            new_partner = message.partner_ids.filtered(
                lambda partner: partner.email == self.partner_email)
            if new_partner:
                self.search([
                    ('partner_id', '=', False),
                    ('partner_email', '=', new_partner.email),
                    ('stage_id.is_cancel', '=', False),
                ]).write({'partner_id': new_partner.id})
        return super(Track, self)._message_post_after_hook(message, msg_vals)

    def open_track_speakers_list(self):
        return {
            'name': _('Speakers'),
            'domain': [('id', 'in', self.mapped('partner_id').ids)],
            'view_mode': 'kanban,form',
            'res_model': 'res.partner',
            'view_id': False,
            'type': 'ir.actions.act_window',
        }
Beispiel #24
0
class EventEvent(models.Model):
    """Event"""
    _name = 'event.event'
    _description = 'Event'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _order = 'date_begin'

    name = fields.Char(string='Event',
                       translate=True,
                       required=True,
                       readonly=False,
                       states={'done': [('readonly', True)]})
    active = fields.Boolean(default=True)
    user_id = fields.Many2one('res.users',
                              string='Responsible',
                              default=lambda self: self.env.user,
                              tracking=True,
                              readonly=False,
                              states={'done': [('readonly', True)]})
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 change_default=True,
                                 default=lambda self: self.env.company,
                                 required=False,
                                 readonly=False,
                                 states={'done': [('readonly', True)]})
    organizer_id = fields.Many2one(
        'res.partner',
        string='Organizer',
        tracking=True,
        default=lambda self: self.env.company.partner_id,
        domain=
        "['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    event_type_id = fields.Many2one('event.type',
                                    string='Category',
                                    readonly=False,
                                    states={'done': [('readonly', True)]})
    color = fields.Integer('Kanban Color Index')
    event_mail_ids = fields.One2many('event.mail',
                                     'event_id',
                                     string='Mail Schedule',
                                     copy=True)

    # Seats and computation
    seats_max = fields.Integer(
        string='Maximum Attendees Number',
        readonly=True,
        states={
            'draft': [('readonly', False)],
            'confirm': [('readonly', False)]
        },
        help=
        "For each event you can define a maximum registration of seats(number of attendees), above this numbers the registrations are not accepted."
    )
    seats_availability = fields.Selection([('limited', 'Limited'),
                                           ('unlimited', 'Unlimited')],
                                          'Maximum Attendees',
                                          required=True,
                                          default='unlimited')
    seats_min = fields.Integer(
        string='Minimum Attendees',
        help=
        "For each event you can define a minimum reserved seats (number of attendees), if it does not reach the mentioned registrations the event can not be confirmed (keep 0 to ignore this rule)"
    )
    seats_reserved = fields.Integer(string='Reserved Seats',
                                    store=True,
                                    readonly=True,
                                    compute='_compute_seats')
    seats_available = fields.Integer(string='Available Seats',
                                     store=True,
                                     readonly=True,
                                     compute='_compute_seats')
    seats_unconfirmed = fields.Integer(string='Unconfirmed Seat Reservations',
                                       store=True,
                                       readonly=True,
                                       compute='_compute_seats')
    seats_used = fields.Integer(string='Number of Participants',
                                store=True,
                                readonly=True,
                                compute='_compute_seats')
    seats_expected = fields.Integer(string='Number of Expected Attendees',
                                    compute_sudo=True,
                                    readonly=True,
                                    compute='_compute_seats')

    # Registration fields
    registration_ids = fields.One2many('event.registration',
                                       'event_id',
                                       string='Attendees',
                                       readonly=False,
                                       states={'done': [('readonly', True)]})
    # Date fields
    date_tz = fields.Selection('_tz_get',
                               string='Timezone',
                               required=True,
                               default=lambda self: self.env.user.tz or 'UTC')
    date_begin = fields.Datetime(string='Start Date',
                                 required=True,
                                 tracking=True,
                                 states={'done': [('readonly', True)]})
    date_end = fields.Datetime(string='End Date',
                               required=True,
                               tracking=True,
                               states={'done': [('readonly', True)]})
    date_begin_located = fields.Char(string='Start Date Located',
                                     compute='_compute_date_begin_tz')
    date_end_located = fields.Char(string='End Date Located',
                                   compute='_compute_date_end_tz')
    is_one_day = fields.Boolean(compute='_compute_field_is_one_day')

    state = fields.Selection(
        [('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
         ('confirm', 'Confirmed'), ('done', 'Done')],
        string='Status',
        default='draft',
        readonly=True,
        required=True,
        copy=False,
        help=
        "If event is created, the status is 'Draft'. If event is confirmed for the particular dates the status is set to 'Confirmed'. If the event is over, the status is set to 'Done'. If event is cancelled the status is set to 'Cancelled'."
    )
    auto_confirm = fields.Boolean(string='Autoconfirm Registrations')
    is_online = fields.Boolean('Online Event')
    address_id = fields.Many2one(
        'res.partner',
        string='Location',
        default=lambda self: self.env.company.partner_id,
        readonly=False,
        states={'done': [('readonly', True)]},
        domain=
        "['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
        tracking=True)
    country_id = fields.Many2one('res.country',
                                 'Country',
                                 related='address_id.country_id',
                                 store=True,
                                 readonly=False)
    twitter_hashtag = fields.Char('Twitter Hashtag')
    description = fields.Html(string='Description',
                              translate=html_translate,
                              sanitize_attributes=False,
                              readonly=False,
                              states={'done': [('readonly', True)]})
    # badge fields
    badge_front = fields.Html(string='Badge Front')
    badge_back = fields.Html(string='Badge Back')
    badge_innerleft = fields.Html(string='Badge Inner Left')
    badge_innerright = fields.Html(string='Badge Inner Right')
    event_logo = fields.Html(string='Event Logo')

    @api.depends('seats_max', 'registration_ids.state')
    def _compute_seats(self):
        """ Determine reserved, available, reserved but unconfirmed and used seats. """
        # initialize fields to 0
        for event in self:
            event.seats_unconfirmed = event.seats_reserved = event.seats_used = event.seats_available = 0
        # aggregate registrations by event and by state
        if self.ids:
            state_field = {
                'draft': 'seats_unconfirmed',
                'open': 'seats_reserved',
                'done': 'seats_used',
            }
            query = """ SELECT event_id, state, count(event_id)
                        FROM event_registration
                        WHERE event_id IN %s AND state IN ('draft', 'open', 'done')
                        GROUP BY event_id, state
                    """
            self.env['event.registration'].flush(['event_id', 'state'])
            self._cr.execute(query, (tuple(self.ids), ))
            for event_id, state, num in self._cr.fetchall():
                event = self.browse(event_id)
                event[state_field[state]] += num
        # compute seats_available
        for event in self:
            if event.seats_max > 0:
                event.seats_available = event.seats_max - (
                    event.seats_reserved + event.seats_used)
            event.seats_expected = event.seats_unconfirmed + event.seats_reserved + event.seats_used

    @api.model
    def _tz_get(self):
        return [(x, x) for x in pytz.all_timezones]

    @api.depends('date_tz', 'date_begin')
    def _compute_date_begin_tz(self):
        for event in self:
            if event.date_begin:
                event.date_begin_located = format_datetime(self.env,
                                                           event.date_begin,
                                                           tz=event.date_tz,
                                                           dt_format='medium')
            else:
                event.date_begin_located = False

    @api.depends('date_tz', 'date_end')
    def _compute_date_end_tz(self):
        for event in self:
            if event.date_end:
                event.date_end_located = format_datetime(self.env,
                                                         event.date_end,
                                                         tz=event.date_tz,
                                                         dt_format='medium')
            else:
                event.date_end_located = False

    @api.depends('date_begin', 'date_end', 'date_tz')
    def _compute_field_is_one_day(self):
        for event in self:
            # Need to localize because it could begin late and finish early in
            # another timezone
            event = event.with_context(tz=event.date_tz)
            begin_tz = fields.Datetime.context_timestamp(
                event, event.date_begin)
            end_tz = fields.Datetime.context_timestamp(event, event.date_end)
            event.is_one_day = (begin_tz.date() == end_tz.date())

    @api.onchange('is_online')
    def _onchange_is_online(self):
        if self.is_online:
            self.address_id = False

    @api.onchange('event_type_id')
    def _onchange_type(self):
        if self.event_type_id:
            self.seats_min = self.event_type_id.default_registration_min
            self.seats_max = self.event_type_id.default_registration_max
            if self.event_type_id.default_registration_max:
                self.seats_availability = 'limited'

            if self.event_type_id.auto_confirm:
                self.auto_confirm = self.event_type_id.auto_confirm

            if self.event_type_id.use_hashtag:
                self.twitter_hashtag = self.event_type_id.default_hashtag

            if self.event_type_id.use_timezone:
                self.date_tz = self.event_type_id.default_timezone

            self.is_online = self.event_type_id.is_online

            if self.event_type_id.event_type_mail_ids:
                self.event_mail_ids = [(5, 0, 0)] + [(0, 0, {
                    attribute_name: line[attribute_name]
                    for attribute_name in self.env['event.type.mail'].
                    _get_event_mail_fields_whitelist()
                }) for line in self.event_type_id.event_type_mail_ids]

    @api.constrains('seats_min', 'seats_max', 'seats_availability')
    def _check_seats_min_max(self):
        if any(event.seats_availability == 'limited'
               and event.seats_min > event.seats_max for event in self):
            raise ValidationError(
                _('Maximum attendees number should be greater than minimum attendees number.'
                  ))

    @api.constrains('seats_max', 'seats_available')
    def _check_seats_limit(self):
        if any(event.seats_availability == 'limited' and event.seats_max
               and event.seats_available < 0 for event in self):
            raise ValidationError(_('No more available seats.'))

    @api.constrains('date_begin', 'date_end')
    def _check_closing_date(self):
        for event in self:
            if event.date_end < event.date_begin:
                raise ValidationError(
                    _('The closing date cannot be earlier than the beginning date.'
                      ))

    @api.depends('name', 'date_begin', 'date_end')
    def name_get(self):
        result = []
        for event in self:
            date_begin = fields.Datetime.from_string(event.date_begin)
            date_end = fields.Datetime.from_string(event.date_end)
            dates = [
                fields.Date.to_string(
                    fields.Datetime.context_timestamp(event, dt))
                for dt in [date_begin, date_end] if dt
            ]
            dates = sorted(set(dates))
            result.append(
                (event.id, '%s (%s)' % (event.name, ' - '.join(dates))))
        return result

    @api.model
    def create(self, vals):
        res = super(EventEvent, self).create(vals)
        if res.organizer_id:
            res.message_subscribe([res.organizer_id.id])
        if res.auto_confirm:
            res.button_confirm()
        return res

    def write(self, vals):
        res = super(EventEvent, self).write(vals)
        if vals.get('organizer_id'):
            self.message_subscribe([vals['organizer_id']])
        return res

    @api.returns('self', lambda value: value.id)
    def copy(self, default=None):
        self.ensure_one()
        default = dict(default or {}, name=_("%s (copy)") % (self.name))
        return super(EventEvent, self).copy(default)

    def button_draft(self):
        self.write({'state': 'draft'})

    def button_cancel(self):
        if any('done' in event.mapped('registration_ids.state')
               for event in self):
            raise UserError(
                _("There are already attendees who attended this event. Please reset it to draft if you want to cancel this event."
                  ))
        self.registration_ids.write({'state': 'cancel'})
        self.state = 'cancel'

    def button_done(self):
        self.write({'state': 'done'})

    def button_confirm(self):
        self.write({'state': 'confirm'})

    def mail_attendees(self,
                       template_id,
                       force_send=False,
                       filter_func=lambda self: self.state != 'cancel'):
        for event in self:
            for attendee in event.registration_ids.filtered(filter_func):
                self.env['mail.template'].browse(template_id).send_mail(
                    attendee.id, force_send=force_send)

    def _is_event_registrable(self):
        return self.date_end > fields.Datetime.now()

    def _get_ics_file(self):
        """ Returns iCalendar file for the event invitation.
            :returns a dict of .ics file content for each event
        """
        result = {}
        if not vobject:
            return result

        for event in self:
            cal = vobject.iCalendar()
            cal_event = cal.add('vevent')

            cal_event.add('created').value = fields.Datetime.now().replace(
                tzinfo=pytz.timezone('UTC'))
            cal_event.add('dtstart').value = fields.Datetime.from_string(
                event.date_begin).replace(tzinfo=pytz.timezone('UTC'))
            cal_event.add('dtend').value = fields.Datetime.from_string(
                event.date_end).replace(tzinfo=pytz.timezone('UTC'))
            cal_event.add('summary').value = event.name
            if event.address_id:
                cal_event.add(
                    'location').value = event.sudo().address_id.contact_address

            result[event.id] = cal.serialize().encode('utf-8')
        return result
Beispiel #25
0
class Invite(models.TransientModel):
    """ Wizard to invite partners (or channels) and make them followers. """
    _name = 'mail.wizard.invite'
    _description = 'Invite wizard'

    @api.model
    def default_get(self, fields):
        result = super(Invite, self).default_get(fields)
        if self._context.get('mail_invite_follower_channel_only'):
            result['send_mail'] = False
        if 'message' not in fields:
            return result

        user_name = self.env.user.name_get()[0][1]
        model = result.get('res_model')
        res_id = result.get('res_id')
        if model and res_id:
            document = self.env['ir.model']._get(model).display_name
            title = self.env[model].browse(res_id).display_name
            msg_fmt = _(
                '%(user_name)s invited you to follow %(document)s document: %(title)s'
            )
        else:
            msg_fmt = _('%(user_name)s invited you to follow a new document.')

        text = msg_fmt % locals()
        message = html.DIV(html.P(_('Hello,')), html.P(text))
        result['message'] = etree.tostring(message)
        return result

    res_model = fields.Char('Related Document Model',
                            required=True,
                            index=True,
                            help='Model of the followed resource')
    res_id = fields.Integer('Related Document ID',
                            index=True,
                            help='Id of the followed resource')
    partner_ids = fields.Many2many(
        'res.partner',
        string='Recipients',
        help=
        "List of partners that will be added as follower of the current document."
    )
    channel_ids = fields.Many2many(
        'mail.channel',
        string='Channels',
        help=
        'List of channels that will be added as listeners of the current document.',
        domain=[('channel_type', '=', 'channel')])
    message = fields.Html('Message')
    send_mail = fields.Boolean(
        'Send Email',
        default=True,
        help=
        "If checked, the partners will receive an email warning they have been added in the document's followers."
    )

    @api.multi
    def add_followers(self):
        email_from = self.env['mail.message']._get_default_from()
        for wizard in self:
            Model = self.env[wizard.res_model]
            document = Model.browse(wizard.res_id)

            # filter partner_ids to get the new followers, to avoid sending email to already following partners
            new_partners = wizard.partner_ids - document.message_partner_ids
            new_channels = wizard.channel_ids - document.message_channel_ids
            document.message_subscribe(new_partners.ids, new_channels.ids)

            model_name = self.env['ir.model']._get(
                wizard.res_model).display_name
            # send an email if option checked and if a message exists (do not send void emails)
            if wizard.send_mail and wizard.message and not wizard.message == '<br>':  # when deleting the message, cleditor keeps a <br>
                message = self.env['mail.message'].create({
                    'subject':
                    _('Invitation to follow %s: %s') %
                    (model_name, document.name_get()[0][1]),
                    'body':
                    wizard.message,
                    'record_name':
                    document.name_get()[0][1],
                    'email_from':
                    email_from,
                    'reply_to':
                    email_from,
                    'model':
                    wizard.res_model,
                    'res_id':
                    wizard.res_id,
                    'no_auto_thread':
                    True,
                    'add_sign':
                    True,
                })
                self.env['res.partner'].with_context(auto_delete=True)._notify(
                    message, [{
                        'id': pid,
                        'share': True,
                        'notif': 'email',
                        'type': 'customer',
                        'groups': []
                    } for pid in new_partners.ids],
                    document,
                    force_send=True,
                    send_after_commit=False)
                message.unlink()
        return {'type': 'ir.actions.act_window_close'}
Beispiel #26
0
class MailTemplate(models.Model):
    "Templates for sending email"
    _name = "mail.template"
    _description = 'Email Templates'
    _order = 'name'

    @api.model
    def default_get(self, fields):
        res = super(MailTemplate, self).default_get(fields)
        if res.get('model'):
            res['model_id'] = self.env['ir.model']._get(res.pop('model')).id
        return res

    name = fields.Char('Name')
    model_id = fields.Many2one(
        'ir.model',
        'Applies to',
        help="The type of document this template can be used with")
    model = fields.Char('Related Document Model',
                        related='model_id.model',
                        index=True,
                        store=True,
                        readonly=True)
    lang = fields.Char(
        'Language',
        help=
        "Optional translation language (ISO code) to select when sending out an email. "
        "If not set, the english version will be used. "
        "This should usually be a placeholder expression "
        "that provides the appropriate language, e.g. "
        "${object.partner_id.lang}.",
        placeholder="${object.partner_id.lang}")
    user_signature = fields.Boolean(
        'Add Signature',
        help=
        "If checked, the user's signature will be appended to the text version "
        "of the message")
    subject = fields.Char('Subject',
                          translate=True,
                          help="Subject (placeholders may be used here)")
    email_from = fields.Char(
        'From',
        help=
        "Sender address (placeholders may be used here). If not set, the default "
        "value will be the author's email alias if configured, or email address."
    )
    use_default_to = fields.Boolean(
        'Default recipients',
        help="Default recipients of the record:\n"
        "- partner (using id on a partner or the partner_id field) OR\n"
        "- email (using email_from or email field)")
    email_to = fields.Char(
        'To (Emails)',
        help=
        "Comma-separated recipient addresses (placeholders may be used here)")
    partner_to = fields.Char(
        'To (Partners)',
        help=
        "Comma-separated ids of recipient partners (placeholders may be used here)"
    )
    email_cc = fields.Char(
        'Cc', help="Carbon copy recipients (placeholders may be used here)")
    reply_to = fields.Char(
        'Reply-To',
        help="Preferred response address (placeholders may be used here)")
    mail_server_id = fields.Many2one(
        'ir.mail_server',
        'Outgoing Mail Server',
        readonly=False,
        help=
        "Optional preferred server for outgoing mails. If not set, the highest "
        "priority one will be used.")
    body_html = fields.Html('Body', translate=True, sanitize=False)
    report_name = fields.Char(
        'Report Filename',
        translate=True,
        help=
        "Name to use for the generated report file (may contain placeholders)\n"
        "The extension can be omitted and will then come from the report type."
    )
    report_template = fields.Many2one('ir.actions.report',
                                      'Optional report to print and attach')
    ref_ir_act_window = fields.Many2one(
        'ir.actions.act_window',
        'Sidebar action',
        readonly=True,
        copy=False,
        help="Sidebar action to make this template available on records "
        "of the related document model")
    attachment_ids = fields.Many2many(
        'ir.attachment',
        'email_template_attachment_rel',
        'email_template_id',
        'attachment_id',
        'Attachments',
        help="You may attach files to this template, to be added to all "
        "emails created from this template")
    auto_delete = fields.Boolean(
        'Auto Delete',
        default=True,
        help="Permanently delete this email after sending it, to save space")

    # Fake fields used to implement the placeholder assistant
    model_object_field = fields.Many2one(
        'ir.model.fields',
        string="Field",
        help="Select target field from the related document model.\n"
        "If it is a relationship field you will be able to select "
        "a target field at the destination of the relationship.")
    sub_object = fields.Many2one(
        'ir.model',
        'Sub-model',
        readonly=True,
        help="When a relationship field is selected as first field, "
        "this field shows the document model the relationship goes to.")
    sub_model_object_field = fields.Many2one(
        'ir.model.fields',
        'Sub-field',
        help="When a relationship field is selected as first field, "
        "this field lets you select the target field within the "
        "destination document model (sub-model).")
    null_value = fields.Char(
        'Default Value',
        help="Optional value to use if the target field is empty")
    copyvalue = fields.Char(
        'Placeholder Expression',
        help=
        "Final placeholder expression, to be copy-pasted in the desired template field."
    )
    scheduled_date = fields.Char(
        'Scheduled Date',
        help=
        "If set, the queue manager will send the email after the date. If not set, the email will be send as soon as possible. Jinja2 placeholders may be used."
    )

    @api.onchange('model_id')
    def onchange_model_id(self):
        # TDE CLEANME: should'nt it be a stored related ?
        if self.model_id:
            self.model = self.model_id.model
        else:
            self.model = False

    def build_expression(self, field_name, sub_field_name, null_value):
        """Returns a placeholder expression for use in a template field,
        based on the values provided in the placeholder assistant.

        :param field_name: main field name
        :param sub_field_name: sub field name (M2O)
        :param null_value: default value if the target value is empty
        :return: final placeholder expression """
        expression = ''
        if field_name:
            expression = "${object." + field_name
            if sub_field_name:
                expression += "." + sub_field_name
            if null_value:
                expression += " or '''%s'''" % null_value
            expression += "}"
        return expression

    @api.onchange('model_object_field', 'sub_model_object_field', 'null_value')
    def onchange_sub_model_object_value_field(self):
        if self.model_object_field:
            if self.model_object_field.ttype in [
                    'many2one', 'one2many', 'many2many'
            ]:
                model = self.env['ir.model']._get(
                    self.model_object_field.relation)
                if model:
                    self.sub_object = model.id
                    self.copyvalue = self.build_expression(
                        self.model_object_field.name,
                        self.sub_model_object_field
                        and self.sub_model_object_field.name or False,
                        self.null_value or False)
            else:
                self.sub_object = False
                self.sub_model_object_field = False
                self.copyvalue = self.build_expression(
                    self.model_object_field.name, False, self.null_value
                    or False)
        else:
            self.sub_object = False
            self.copyvalue = False
            self.sub_model_object_field = False
            self.null_value = False

    def unlink(self):
        self.unlink_action()
        return super(MailTemplate, self).unlink()

    @api.returns('self', lambda value: value.id)
    def copy(self, default=None):
        default = dict(default or {}, name=_("%s (copy)") % self.name)
        return super(MailTemplate, self).copy(default=default)

    def unlink_action(self):
        for template in self:
            if template.ref_ir_act_window:
                template.ref_ir_act_window.unlink()
        return True

    def create_action(self):
        ActWindow = self.env['ir.actions.act_window']
        view = self.env.ref('mail.email_compose_message_wizard_form')

        for template in self:
            button_name = _('Send Mail (%s)') % template.name
            action = ActWindow.create({
                'name':
                button_name,
                'type':
                'ir.actions.act_window',
                'res_model':
                'mail.compose.message',
                'context':
                "{'default_composition_mode': 'mass_mail', 'default_template_id' : %d, 'default_use_template': True}"
                % (template.id),
                'view_mode':
                'form,tree',
                'view_id':
                view.id,
                'target':
                'new',
                'binding_model_id':
                template.model_id.id,
            })
            template.write({'ref_ir_act_window': action.id})

        return True

    # ----------------------------------------
    # RENDERING
    # ----------------------------------------

    @api.model
    def render_post_process(self, html):
        html = self.env['mail.thread']._replace_local_links(html)
        return html

    @api.model
    def _render_template(self,
                         template_txt,
                         model,
                         res_ids,
                         post_process=False):
        """ Render the given template text, replace mako expressions ``${expr}``
        with the result of evaluating these expressions with an evaluation
        context containing:

         - ``user``: Model of the current user
         - ``object``: record of the document record this mail is related to
         - ``context``: the context passed to the mail composition wizard

        :param str template_txt: the template text to render
        :param str model: model name of the document record this mail is related to.
        :param int res_ids: list of ids of document records those mails are related to.
        """
        multi_mode = True
        if isinstance(res_ids, int):
            multi_mode = False
            res_ids = [res_ids]

        results = dict.fromkeys(res_ids, u"")

        # try to load the template
        try:
            mako_env = mako_safe_template_env if self.env.context.get(
                'safe') else mako_template_env
            template = mako_env.from_string(tools.ustr(template_txt))
        except Exception:
            _logger.info("Failed to load template %r",
                         template_txt,
                         exc_info=True)
            return multi_mode and results or results[res_ids[0]]

        # prepare template variables
        records = self.env[model].browse(
            it for it in res_ids if it)  # filter to avoid browsing [None]
        res_to_rec = dict.fromkeys(res_ids, None)
        for record in records:
            res_to_rec[record.id] = record
        variables = {
            'format_date':
            lambda date, date_format=False, lang_code=False: format_date(
                self.env, date, date_format, lang_code),
            'format_datetime':
            lambda dt, tz=False, dt_format=False, lang_code=False:
            format_datetime(self.env, dt, tz, dt_format, lang_code),
            'format_amount':
            lambda amount, currency, lang_code=False: tools.format_amount(
                self.env, amount, currency, lang_code),
            'format_duration':
            lambda value: tools.format_duration(value),
            'user':
            self.env.user,
            'ctx':
            self._context,  # context kw would clash with mako internals
        }
        for res_id, record in res_to_rec.items():
            variables['object'] = record
            try:
                render_result = template.render(variables)
            except Exception:
                _logger.info("Failed to render template %r using values %r" %
                             (template, variables),
                             exc_info=True)
                raise UserError(
                    _("Failed to render template %r using values %r") %
                    (template, variables))
            if render_result == u"False":
                render_result = u""
            results[res_id] = render_result

        if post_process:
            for res_id, result in results.items():
                results[res_id] = self.render_post_process(result)

        return multi_mode and results or results[res_ids[0]]

    def get_email_template(self, res_ids):
        multi_mode = True
        if isinstance(res_ids, int):
            res_ids = [res_ids]
            multi_mode = False

        if res_ids is None:
            res_ids = [None]
        results = dict.fromkeys(res_ids, False)

        if not self.ids:
            return results
        self.ensure_one()

        if self.env.context.get('template_preview_lang'):
            lang = self.env.context.get('template_preview_lang')
            for res_id in res_ids:
                results[res_id] = self.with_context(lang=lang)
        else:
            langs = self._render_template(self.lang, self.model, res_ids)
            for res_id, lang in langs.items():
                if lang:
                    template = self.with_context(lang=lang)
                else:
                    template = self
                results[res_id] = template

        return multi_mode and results or results[res_ids[0]]

    def generate_recipients(self, results, res_ids):
        """Generates the recipients of the template. Default values can ben generated
        instead of the template values if requested by template or context.
        Emails (email_to, email_cc) can be transformed into partners if requested
        in the context. """
        self.ensure_one()

        if self.use_default_to or self._context.get('tpl_force_default_to'):
            records = self.env[self.model].browse(res_ids).sudo()
            default_recipients = self.env[
                'mail.thread']._message_get_default_recipients_on_records(
                    records)
            for res_id, recipients in default_recipients.items():
                results[res_id].pop('partner_to', None)
                results[res_id].update(recipients)

        records_company = None
        if self._context.get(
                'tpl_partners_only'
        ) and self.model and results and 'company_id' in self.env[
                self.model]._fields:
            records = self.env[self.model].browse(results.keys()).read(
                ['company_id'])
            records_company = {
                rec['id']:
                (rec['company_id'][0] if rec['company_id'] else None)
                for rec in records
            }

        for res_id, values in results.items():
            partner_ids = values.get('partner_ids', list())
            if self._context.get('tpl_partners_only'):
                mails = tools.email_split(values.pop(
                    'email_to', '')) + tools.email_split(
                        values.pop('email_cc', ''))
                Partner = self.env['res.partner']
                if records_company:
                    Partner = Partner.with_context(
                        default_company_id=records_company[res_id])
                for mail in mails:
                    partner_id = Partner.find_or_create(mail)
                    partner_ids.append(partner_id)
            partner_to = values.pop('partner_to', '')
            if partner_to:
                # placeholders could generate '', 3, 2 due to some empty field values
                tpl_partner_ids = [
                    int(pid) for pid in partner_to.split(',') if pid
                ]
                partner_ids += self.env['res.partner'].sudo().browse(
                    tpl_partner_ids).exists().ids
            results[res_id]['partner_ids'] = partner_ids
        return results

    def generate_email(self, res_ids, fields=None):
        """Generates an email from the template for given the given model based on
        records given by res_ids.

        :param res_id: id of the record to use for rendering the template (model
                       is taken from template definition)
        :returns: a dict containing all relevant fields for creating a new
                  mail.mail entry, with one extra key ``attachments``, in the
                  format [(report_name, data)] where data is base64 encoded.
        """
        self.ensure_one()
        multi_mode = True
        if isinstance(res_ids, int):
            res_ids = [res_ids]
            multi_mode = False
        if fields is None:
            fields = [
                'subject', 'body_html', 'email_from', 'email_to', 'partner_to',
                'email_cc', 'reply_to', 'scheduled_date'
            ]

        res_ids_to_templates = self.get_email_template(res_ids)

        # templates: res_id -> template; template -> res_ids
        templates_to_res_ids = {}
        for res_id, template in res_ids_to_templates.items():
            templates_to_res_ids.setdefault(template, []).append(res_id)

        results = dict()
        for template, template_res_ids in templates_to_res_ids.items():
            Template = self.env['mail.template']
            # generate fields value for all res_ids linked to the current template
            if template.lang:
                Template = Template.with_context(
                    lang=template._context.get('lang'))
            for field in fields:
                Template = Template.with_context(safe=field in {'subject'})
                generated_field_values = Template._render_template(
                    getattr(template, field),
                    template.model,
                    template_res_ids,
                    post_process=(field == 'body_html'))
                for res_id, field_value in generated_field_values.items():
                    results.setdefault(res_id, dict())[field] = field_value
            # compute recipients
            if any(field in fields
                   for field in ['email_to', 'partner_to', 'email_cc']):
                results = template.generate_recipients(results,
                                                       template_res_ids)
            # update values for all res_ids
            for res_id in template_res_ids:
                values = results[res_id]
                # body: add user signature, sanitize
                if 'body_html' in fields and template.user_signature:
                    signature = self.env.user.signature
                    if signature:
                        values['body_html'] = tools.append_content_to_html(
                            values['body_html'], signature, plaintext=False)
                if values.get('body_html'):
                    values['body'] = tools.html_sanitize(values['body_html'])
                # technical settings
                values.update(
                    mail_server_id=template.mail_server_id.id or False,
                    auto_delete=template.auto_delete,
                    model=template.model,
                    res_id=res_id or False,
                    attachment_ids=[
                        attach.id for attach in template.attachment_ids
                    ],
                )

            # Add report in attachments: generate once for all template_res_ids
            if template.report_template:
                for res_id in template_res_ids:
                    attachments = []
                    report_name = self._render_template(
                        template.report_name, template.model, res_id)
                    report = template.report_template
                    report_service = report.report_name

                    if report.report_type in ['qweb-html', 'qweb-pdf']:
                        result, format = report.render_qweb_pdf([res_id])
                    else:
                        res = report.render([res_id])
                        if not res:
                            raise UserError(
                                _('Unsupported report type %s found.') %
                                report.report_type)
                        result, format = res

                    # TODO in trunk, change return format to binary to match message_post expected format
                    result = base64.b64encode(result)
                    if not report_name:
                        report_name = 'report.' + report_service
                    ext = "." + format
                    if not report_name.endswith(ext):
                        report_name += ext
                    attachments.append((report_name, result))
                    results[res_id]['attachments'] = attachments

        return multi_mode and results or results[res_ids[0]]

    # ----------------------------------------
    # EMAIL
    # ----------------------------------------

    def send_mail(self,
                  res_id,
                  force_send=False,
                  raise_exception=False,
                  email_values=None,
                  notif_layout=False):
        """ Generates a new mail.mail. Template is rendered on record given by
        res_id and model coming from template.

        :param int res_id: id of the record to render the template
        :param bool force_send: send email immediately; otherwise use the mail
            queue (recommended);
        :param dict email_values: update generated mail with those values to further
            customize the mail;
        :param str notif_layout: optional notification layout to encapsulate the
            generated email;
        :returns: id of the mail.mail that was created """
        self.ensure_one()
        Mail = self.env['mail.mail']
        Attachment = self.env[
            'ir.attachment']  # TDE FIXME: should remove default_type from context

        # create a mail_mail based on values, without attachments
        values = self.generate_email(res_id)
        values['recipient_ids'] = [
            (4, pid) for pid in values.get('partner_ids', list())
        ]
        values['attachment_ids'] = [
            (4, aid) for aid in values.get('attachment_ids', list())
        ]
        values.update(email_values or {})
        attachment_ids = values.pop('attachment_ids', [])
        attachments = values.pop('attachments', [])
        # add a protection against void email_from
        if 'email_from' in values and not values.get('email_from'):
            values.pop('email_from')
        # encapsulate body
        if notif_layout and values['body_html']:
            try:
                template = self.env.ref(notif_layout, raise_if_not_found=True)
            except ValueError:
                _logger.warning(
                    'QWeb template %s not found when sending template %s. Sending without layouting.'
                    % (notif_layout, self.name))
            else:
                record = self.env[self.model].browse(res_id)
                template_ctx = {
                    'message':
                    self.env['mail.message'].sudo().new(
                        dict(body=values['body_html'],
                             record_name=record.display_name)),
                    'model_description':
                    self.env['ir.model']._get(record._name).display_name,
                    'company':
                    'company_id' in record and record['company_id']
                    or self.env.company,
                    'record':
                    record,
                }
                body = template.render(template_ctx,
                                       engine='ir.qweb',
                                       minimal_qcontext=True)
                values['body_html'] = self.env[
                    'mail.thread']._replace_local_links(body)
        mail = Mail.create(values)

        # manage attachments
        for attachment in attachments:
            attachment_data = {
                'name': attachment[0],
                'datas': attachment[1],
                'type': 'binary',
                'res_model': 'mail.message',
                'res_id': mail.mail_message_id.id,
            }
            attachment_ids.append((4, Attachment.create(attachment_data).id))
        if attachment_ids:
            mail.write({'attachment_ids': attachment_ids})

        if force_send:
            mail.send(raise_exception=raise_exception)
        return mail.id  # TDE CLEANME: return mail + api.returns ?
Beispiel #27
0
class MailTemplate(models.Model):

    _inherit = ['mail.template', 'website_dependent.mixin']
    _name = 'mail.template'

    body_html = fields.Html(company_dependent=True, website_dependent=True)
    mail_server_id = fields.Many2one(
        string='Outgoing Mail Server (Multi-Website)',
        company_dependent=True,
        website_dependent=True)
    report_template = fields.Many2one(
        string='Optional report to print and attach (Multi-Website)',
        company_dependent=True,
        website_dependent=True)

    @api.multi
    def generate_email(self, res_ids, fields=None):
        """Remove mail_server_id when not set to recompute in _default_mail_server_id in mail.message"""
        multi_mode = True
        if isinstance(res_ids, pycompat.integer_types):
            multi_mode = False
        res = super(MailTemplate, self).generate_email(res_ids, fields=fields)
        if not multi_mode:
            list_of_dict = {0: res}
        else:
            list_of_dict = res

        for _unused, data in list_of_dict.items():
            if 'mail_server_id' in data and not data.get('mail_server_id'):
                del data['mail_server_id']

        return res

    @api.model
    def _render_template(self,
                         template_txt,
                         model,
                         res_ids,
                         post_process=False):
        """Override to add website to context"""
        multi_mode = True
        if isinstance(res_ids, pycompat.integer_types):
            multi_mode = False
            res_ids = [res_ids]

        results = dict.fromkeys(res_ids, u"")

        # try to load the template
        try:
            mako_env = mako_safe_template_env if self.env.context.get(
                'safe') else mako_template_env
            template = mako_env.from_string(tools.ustr(template_txt))
        except Exception:
            _logger.info("Failed to load template %r",
                         template_txt,
                         exc_info=True)
            return multi_mode and results or results[res_ids[0]]

        # prepare template variables
        records = self.env[model].browse(
            it for it in res_ids if it)  # filter to avoid browsing [None]
        res_to_rec = dict.fromkeys(res_ids, None)
        for record in records:
            res_to_rec[record.id] = record

        variables = {
            'format_date':
            lambda date, format=False, context=self._context: format_date(
                self.env, date, format),
            'format_tz':
            lambda dt, tz=False, format=False, context=self._context:
            format_tz(self.env, dt, tz, format),
            'format_amount':
            lambda amount, currency, context=self._context: format_amount(
                self.env, amount, currency),
            'user':
            self.env.user,
            'ctx':
            self._context,  # context kw would clash with mako internals
        }

        # [NEW] Check website and company context
        company = self.env['res.company']  # empty value

        company_id = self.env.context.get('force_company')
        if company_id:
            company = self.env['res.company'].sudo().browse(company_id)

        if self.env.context.get('website_id'):
            website = self.env['website'].browse(
                self.env.context.get('website_id'))
        else:
            website = self.env.user.backend_website_id

        for res_id, record in res_to_rec.items():
            record_company = company
            if not record_company:
                if hasattr(record, 'company_id') and record.company_id:
                    record_company = record.company_id

            record_website = website
            if hasattr(record, 'website_id') and record.website_id:
                record_website = record.website_id

            if record_company and record_website \
               and record_website.company_id != company:
                # company and website are incompatible, so keep only company
                record_website = self.env['website']  # empty value

            record_context = dict(force_company=record_company.id,
                                  website_id=record_website.id)
            variables['object'] = record.with_context(**record_context)
            variables['website'] = record_website

            try:
                render_result = template.render(variables)
            except Exception:
                _logger.info("Failed to render template %r using values %r" %
                             (template, variables),
                             exc_info=True)
                raise UserError(
                    _("Failed to render template %r using values %r") %
                    (template, variables))
            if render_result == u"False":
                render_result = u""

            if post_process:
                render_result = self.with_context(
                    **record_context).render_post_process(render_result)

            results[res_id] = render_result

        return multi_mode and results or results[res_ids[0]]

    @api.model
    def create(self, vals):
        res = super(MailTemplate, self).create(vals)
        # make value company independent
        for f in FIELDS:
            res._force_default(f, vals.get(f))
        return res

    @api.multi
    def write(self, vals):
        res = super(MailTemplate, self).write(vals)

        # TODO: will it work with OCA's partner_firstname module?
        if 'name' in vals:
            fields_to_update = FIELDS
        else:
            fields_to_update = [f for f in FIELDS if f in vals]
        for f in fields_to_update:
            self._update_properties_label(f)

        return res

    def _auto_init(self):
        for f in FIELDS:
            self._auto_init_website_dependent(f)
        return super(MailTemplate, self)._auto_init()
Beispiel #28
0
class IrActions(models.Model):
    _name = 'ir.actions.actions'
    _description = 'Actions'
    _table = 'ir_actions'
    _order = 'name'

    name = fields.Char(required=True)
    type = fields.Char(string='Action Type', required=True)
    xml_id = fields.Char(compute='_compute_xml_id', string="External ID")
    help = fields.Html(
        string='Action Description',
        help=
        'Optional help text for the users with a description of the target view, such as its usage and purpose.',
        translate=True)
    binding_model_id = fields.Many2one(
        'ir.model',
        ondelete='cascade',
        help=
        "Setting a value makes this action available in the sidebar for the given model."
    )
    binding_type = fields.Selection([('action', 'Action'),
                                     ('action_form_only', "Form-only"),
                                     ('report', 'Report')],
                                    required=True,
                                    default='action')

    def _compute_xml_id(self):
        res = self.get_external_id()
        for record in self:
            record.xml_id = res.get(record.id)

    @api.model_create_multi
    def create(self, vals_list):
        res = super(IrActions, self).create(vals_list)
        # self.get_bindings() depends on action records
        self.clear_caches()
        return res

    @api.multi
    def write(self, vals):
        res = super(IrActions, self).write(vals)
        # self.get_bindings() depends on action records
        self.clear_caches()
        return res

    @api.multi
    def unlink(self):
        """unlink ir.action.todo which are related to actions which will be deleted.
           NOTE: ondelete cascade will not work on ir.actions.actions so we will need to do it manually."""
        todos = self.env['ir.actions.todo'].search([('action_id', 'in',
                                                     self.ids)])
        todos.unlink()
        res = super(IrActions, self).unlink()
        # self.get_bindings() depends on action records
        self.clear_caches()
        return res

    @api.model
    def _get_eval_context(self, action=None):
        """ evaluation context to pass to safe_eval """
        return {
            'uid': self._uid,
            'user': self.env.user,
            'time': time,
            'datetime': datetime,
            'dateutil': dateutil,
            'timezone': timezone,
            'b64encode': base64.b64encode,
            'b64decode': base64.b64decode,
        }

    @api.model
    @tools.ormcache('frozenset(self.env.user.groups_id.ids)', 'model_name')
    def get_bindings(self, model_name):
        """ Retrieve the list of actions bound to the given model.

           :return: a dict mapping binding types to a list of dict describing
                    actions, where the latter is given by calling the method
                    ``read`` on the action record.
        """
        cr = self.env.cr
        query = """ SELECT a.id, a.type, a.binding_type
                    FROM ir_actions a, ir_model m
                    WHERE a.binding_model_id=m.id AND m.model=%s
                    ORDER BY a.id """
        cr.execute(query, [model_name])

        # discard unauthorized actions, and read action definitions
        result = defaultdict(list)
        user_groups = self.env.user.groups_id
        for action_id, action_model, binding_type in cr.fetchall():
            try:
                action = self.env[action_model].browse(action_id)
                action_groups = getattr(action, 'groups_id', ())
                if action_groups and not action_groups & user_groups:
                    # the user may not perform this action
                    continue
                result[binding_type].append(action.read()[0])
            except (AccessError, MissingError):
                continue

        return result
Beispiel #29
0
class SurveyQuestion(models.Model):
    """ Questions that will be asked in a survey.

        Each question can have one of more suggested answers (eg. in case of
        dropdown choices, multi-answer checkboxes, radio buttons...).

        Technical note:

        survey.question is also the model used for the survey's pages (with the "is_page" field set to True).

        A page corresponds to a "section" in the interface, and the fact that it separates the survey in
        actual pages in the interface depends on the "questions_layout" parameter on the survey.survey model.
        Pages are also used when randomizing questions. The randomization can happen within a "page".

        Using the same model for questions and pages allows to put all the pages and questions together in a o2m field
        (see survey.survey.question_and_page_ids) on the view side and easily reorganize your survey by dragging the
        items around.

        It also removes on level of encoding by directly having 'Add a page' and 'Add a question'
        links on the tree view of questions, enabling a faster encoding.

        However, this has the downside of making the code reading a little bit more complicated.
        Efforts were made at the model level to create computed fields so that the use of these models
        still seems somewhat logical. That means:
        - A survey still has "page_ids" (question_and_page_ids filtered on is_page = True)
        - These "page_ids" still have question_ids (questions located between this page and the next)
        - These "question_ids" still have a "page_id"

        That makes the use and display of these information at view and controller levels easier to understand.
    """

    _name = 'survey.question'
    _description = 'Survey Question'
    _rec_name = 'question'
    _order = 'sequence,id'

    @api.model
    def default_get(self, fields):
        defaults = super(SurveyQuestion, self).default_get(fields)
        if (not fields or 'question_type' in fields):
            defaults['question_type'] = False if defaults.get(
                'is_page') == True else 'free_text'
        return defaults

    # Question metadata
    survey_id = fields.Many2one('survey.survey',
                                string='Survey',
                                ondelete='cascade')
    page_id = fields.Many2one('survey.question',
                              string='Page',
                              compute="_compute_page_id",
                              store=True)
    question_ids = fields.One2many('survey.question',
                                   string='Questions',
                                   compute="_compute_question_ids")
    scoring_type = fields.Selection(related='survey_id.scoring_type',
                                    string='Scoring Type',
                                    readonly=True)
    sequence = fields.Integer('Sequence', default=10)
    # Question
    is_page = fields.Boolean('Is a page?')
    questions_selection = fields.Selection(
        related='survey_id.questions_selection',
        readonly=True,
        help=
        "If randomized is selected, add the number of random questions next to the section."
    )
    random_questions_count = fields.Integer(
        'Random questions count',
        default=1,
        help=
        "Used on randomized sections to take X random questions from all the questions of that section."
    )
    title = fields.Char('Title', required=True, translate=True)
    question = fields.Char('Question', related="title")
    description = fields.Html(
        'Description',
        help=
        "Use this field to add additional explanations about your question",
        translate=True)
    question_type = fields.Selection(
        [('free_text', 'Multiple Lines Text Box'),
         ('textbox', 'Single Line Text Box'),
         ('numerical_box', 'Numerical Value'), ('date', 'Date'),
         ('datetime', 'Datetime'),
         ('simple_choice', 'Multiple choice: only one answer'),
         ('multiple_choice', 'Multiple choice: multiple answers allowed'),
         ('matrix', 'Matrix')],
        string='Question Type')
    # simple choice / multiple choice / matrix
    labels_ids = fields.One2many(
        'survey.label',
        'question_id',
        string='Types of answers',
        copy=True,
        help=
        'Labels used for proposed choices: simple choice, multiple choice and columns of matrix'
    )
    # matrix
    matrix_subtype = fields.Selection(
        [('simple', 'One choice per row'),
         ('multiple', 'Multiple choices per row')],
        string='Matrix Type',
        default='simple')
    labels_ids_2 = fields.One2many(
        'survey.label',
        'question_id_2',
        string='Rows of the Matrix',
        copy=True,
        help='Labels used for proposed choices: rows of matrix')
    # Display options
    column_nb = fields.Selection(
        [('12', '1'), ('6', '2'), ('4', '3'), ('3', '4'), ('2', '6')],
        string='Number of columns',
        default='12',
        help=
        'These options refer to col-xx-[12|6|4|3|2] classes in Bootstrap for dropdown-based simple and multiple choice questions.'
    )
    display_mode = fields.Selection(
        [('columns', 'Radio Buttons'), ('dropdown', 'Selection Box')],
        string='Display Mode',
        default='columns',
        help='Display mode of simple choice questions.')
    # Comments
    comments_allowed = fields.Boolean('Show Comments Field')
    comments_message = fields.Char(
        'Comment Message',
        translate=True,
        default=lambda self: _("If other, please specify:"))
    comment_count_as_answer = fields.Boolean(
        'Comment Field is an Answer Choice')
    # Validation
    validation_required = fields.Boolean('Validate entry')
    validation_email = fields.Boolean('Input must be an email')
    validation_length_min = fields.Integer('Minimum Text Length')
    validation_length_max = fields.Integer('Maximum Text Length')
    validation_min_float_value = fields.Float('Minimum value')
    validation_max_float_value = fields.Float('Maximum value')
    validation_min_date = fields.Date('Minimum Date')
    validation_max_date = fields.Date('Maximum Date')
    validation_min_datetime = fields.Datetime('Minimum Datetime')
    validation_max_datetime = fields.Datetime('Maximum Datetime')
    validation_error_msg = fields.Char(
        'Validation Error message',
        translate=True,
        default=lambda self: _("The answer you entered is not valid."))
    # Constraints on number of answers (matrices)
    constr_mandatory = fields.Boolean('Mandatory Answer')
    constr_error_msg = fields.Char(
        'Error message',
        translate=True,
        default=lambda self: _("This question requires an answer."))
    # Answer
    user_input_line_ids = fields.One2many('survey.user_input_line',
                                          'question_id',
                                          string='Answers',
                                          domain=[('skipped', '=', False)],
                                          groups='survey.group_survey_user')

    _sql_constraints = [
        ('positive_len_min', 'CHECK (validation_length_min >= 0)',
         'A length must be positive!'),
        ('positive_len_max', 'CHECK (validation_length_max >= 0)',
         'A length must be positive!'),
        ('validation_length',
         'CHECK (validation_length_min <= validation_length_max)',
         'Max length cannot be smaller than min length!'),
        ('validation_float',
         'CHECK (validation_min_float_value <= validation_max_float_value)',
         'Max value cannot be smaller than min value!'),
        ('validation_date',
         'CHECK (validation_min_date <= validation_max_date)',
         'Max date cannot be smaller than min date!'),
        ('validation_datetime',
         'CHECK (validation_min_datetime <= validation_max_datetime)',
         'Max datetime cannot be smaller than min datetime!')
    ]

    @api.onchange('validation_email')
    def _onchange_validation_email(self):
        if self.validation_email:
            self.validation_required = False

    @api.onchange('is_page')
    def _onchange_is_page(self):
        if self.is_page:
            self.question_type = False

    # Validation methods

    def validate_question(self, post, answer_tag):
        """ Validate question, depending on question type and parameters """
        self.ensure_one()
        try:
            checker = getattr(self, 'validate_' + self.question_type)
        except AttributeError:
            _logger.warning(self.question_type +
                            ": This type of question has no validation method")
            return {}
        else:
            return checker(post, answer_tag)

    def validate_free_text(self, post, answer_tag):
        self.ensure_one()
        errors = {}
        answer = post[answer_tag].strip()
        # Empty answer to mandatory question
        if self.constr_mandatory and not answer:
            errors.update({answer_tag: self.constr_error_msg})
        return errors

    def validate_textbox(self, post, answer_tag):
        self.ensure_one()
        errors = {}
        answer = post[answer_tag].strip()
        # Empty answer to mandatory question
        if self.constr_mandatory and not answer:
            errors.update({answer_tag: self.constr_error_msg})
        # Email format validation
        # Note: this validation is very basic:
        #     all the strings of the form
        #     <something>@<anything>.<extension>
        #     will be accepted
        if answer and self.validation_email:
            if not email_validator.match(answer):
                errors.update(
                    {answer_tag: _('This answer must be an email address')})
        # Answer validation (if properly defined)
        # Length of the answer must be in a range
        if answer and self.validation_required:
            if not (self.validation_length_min <= len(answer) <=
                    self.validation_length_max):
                errors.update({answer_tag: self.validation_error_msg})
        return errors

    def validate_numerical_box(self, post, answer_tag):
        self.ensure_one()
        errors = {}
        answer = post[answer_tag].strip()
        # Empty answer to mandatory question
        if self.constr_mandatory and not answer:
            errors.update({answer_tag: self.constr_error_msg})
        # Checks if user input is a number
        if answer:
            try:
                floatanswer = float(answer)
            except ValueError:
                errors.update({answer_tag: _('This is not a number')})
        # Answer validation (if properly defined)
        if answer and self.validation_required:
            # Answer is not in the right range
            with tools.ignore(Exception):
                floatanswer = float(
                    answer)  # check that it is a float has been done hereunder
                if not (self.validation_min_float_value <= floatanswer <=
                        self.validation_max_float_value):
                    errors.update({answer_tag: self.validation_error_msg})
        return errors

    def date_validation(self, date_type, post, answer_tag, min_value,
                        max_value):
        self.ensure_one()
        errors = {}
        if date_type not in ('date', 'datetime'):
            raise ValueError("Unexpected date type value")
        answer = post[answer_tag].strip()
        # Empty answer to mandatory question
        if self.constr_mandatory and not answer:
            errors.update({answer_tag: self.constr_error_msg})
        # Checks if user input is a date
        if answer:
            try:
                if date_type == 'datetime':
                    dateanswer = fields.Datetime.from_string(answer)
                else:
                    dateanswer = fields.Date.from_string(answer)
            except ValueError:
                errors.update({answer_tag: _('This is not a date')})
                return errors
        # Answer validation (if properly defined)
        if answer and self.validation_required:
            # Answer is not in the right range
            try:
                if date_type == 'datetime':
                    date_from_string = fields.Datetime.from_string
                else:
                    date_from_string = fields.Date.from_string
                dateanswer = date_from_string(answer)
                min_date = date_from_string(min_value)
                max_date = date_from_string(max_value)

                if min_date and max_date and not (min_date <= dateanswer <=
                                                  max_date):
                    # If Minimum and Maximum Date are entered
                    errors.update({answer_tag: self.validation_error_msg})
                elif min_date and not min_date <= dateanswer:
                    # If only Minimum Date is entered and not Define Maximum Date
                    errors.update({answer_tag: self.validation_error_msg})
                elif max_date and not dateanswer <= max_date:
                    # If only Maximum Date is entered and not Define Minimum Date
                    errors.update({answer_tag: self.validation_error_msg})
            except ValueError:  # check that it is a date has been done hereunder
                pass
        return errors

    def validate_date(self, post, answer_tag):
        return self.date_validation('date', post, answer_tag,
                                    self.validation_min_date,
                                    self.validation_max_date)

    def validate_datetime(self, post, answer_tag):
        return self.date_validation('datetime', post, answer_tag,
                                    self.validation_min_datetime,
                                    self.validation_max_datetime)

    def validate_simple_choice(self, post, answer_tag):
        self.ensure_one()
        errors = {}
        if self.comments_allowed:
            comment_tag = "%s_%s" % (answer_tag, 'comment')
        # Empty answer to mandatory self
        if self.constr_mandatory and answer_tag not in post:
            errors.update({answer_tag: self.constr_error_msg})
        if self.constr_mandatory and answer_tag in post and not post[
                answer_tag].strip():
            errors.update({answer_tag: self.constr_error_msg})
        # Answer is a comment and is empty
        if self.constr_mandatory and answer_tag in post and post[
                answer_tag] == "-1" and self.comment_count_as_answer and comment_tag in post and not post[
                    comment_tag].strip():
            errors.update({answer_tag: self.constr_error_msg})
        return errors

    def validate_multiple_choice(self, post, answer_tag):
        self.ensure_one()
        errors = {}
        if self.constr_mandatory:
            answer_candidates = dict_keys_startswith(post, answer_tag)
            comment_flag = answer_candidates.pop(("%s_%s" % (answer_tag, -1)),
                                                 None)
            if self.comments_allowed:
                comment_answer = answer_candidates.pop(
                    ("%s_%s" % (answer_tag, 'comment')), '').strip()
            # Preventing answers with blank value
            if all(not answer.strip() for answer in
                   answer_candidates.values()) and answer_candidates:
                errors.update({answer_tag: self.constr_error_msg})
            # There is no answer neither comments (if comments count as answer)
            if not answer_candidates and self.comment_count_as_answer and (
                    not comment_flag or not comment_answer):
                errors.update({answer_tag: self.constr_error_msg})
            # There is no answer at all
            if not answer_candidates and not self.comment_count_as_answer:
                errors.update({answer_tag: self.constr_error_msg})
        return errors

    def validate_matrix(self, post, answer_tag):
        self.ensure_one()
        errors = {}
        if self.constr_mandatory:
            lines_number = len(self.labels_ids_2)
            answer_candidates = dict_keys_startswith(post, answer_tag)
            answer_candidates.pop(("%s_%s" % (answer_tag, 'comment')),
                                  '').strip()
            # Number of lines that have been answered
            if self.matrix_subtype == 'simple':
                answer_number = len(answer_candidates)
            elif self.matrix_subtype == 'multiple':
                answer_number = len(
                    {sk.rsplit('_', 1)[0]
                     for sk in answer_candidates})
            else:
                raise RuntimeError("Invalid matrix subtype")
            # Validate that each line has been answered
            if answer_number != lines_number:
                errors.update({answer_tag: self.constr_error_msg})
        return errors

    @api.depends('survey_id.question_and_page_ids.is_page',
                 'survey_id.question_and_page_ids.sequence')
    def _compute_question_ids(self):
        """Will take all questions of the survey for which the index is higher than the index of this page
        and lower than the index of the next page."""
        for question in self:
            if question.is_page:
                next_page_index = False
                for page in question.survey_id.page_ids:
                    if page._index() > question._index():
                        next_page_index = page._index()
                        break

                question.question_ids = question.survey_id.question_ids.filtered(
                    lambda q: q._index() > question._index() and
                    (not next_page_index or q._index() < next_page_index))
            else:
                question.question_ids = self.env['survey.question']

    @api.depends('survey_id.question_and_page_ids.is_page',
                 'survey_id.question_and_page_ids.sequence')
    def _compute_page_id(self):
        """Will find the page to which this question belongs to by looking inside the corresponding survey"""
        for question in self:
            if question.is_page:
                question.page_id = None
            else:
                question.page_id = next((iter(
                    question.survey_id.question_and_page_ids.filtered(
                        lambda q: q.is_page and q.sequence < question.sequence
                    ).sorted(reverse=True))), None)

    def _index(self):
        """We would normally just use the 'sequence' field of questions BUT, if the pages and questions are
        created without ever moving records around, the sequence field can be set to 0 for all the questions.

        However, the order of the recordset is always correct so we can rely on the index method."""
        self.ensure_one()
        return list(self.survey_id.question_and_page_ids).index(self)

    def get_correct_answer_ids(self):
        self.ensure_one()

        return self.labels_ids.filtered(lambda label: label.is_correct)
Beispiel #30
0
class HrPlanActivityType(models.Model):
    _name = 'hr.plan.activity.type'
    _description = 'Plan activity type'
    _rec_name = 'summary'

    activity_type_id = fields.Many2one(
        'mail.activity.type',
        'Activity Type',
        default=lambda self: self.env.ref('mail.mail_activity_data_todo'),
        domain=lambda self: [
            '|', ('res_model_id', '=', False),
            ('res_model_id', '=', self.env['ir.model']._get('hr.employee').id)
        ])
    summary = fields.Char('Summary')
    responsible = fields.Selection([('coach', 'Coach'), ('manager', 'Manager'),
                                    ('employee', 'Employee'),
                                    ('other', 'Other')],
                                   default='employee',
                                   string='Responsible',
                                   required=True)
    responsible_id = fields.Many2one(
        'res.users',
        'Responsible Person',
        help='Specific responsible of activity if not linked to the employee.')
    note = fields.Html('Note')

    @api.onchange('activity_type_id')
    def _onchange_activity_type_id(self):
        if self.activity_type_id and self.activity_type_id.summary and not self.summary:
            self.summary = self.activity_type_id.summary

    def get_responsible_id(self, employee):
        if self.responsible == 'coach':
            if not employee.coach_id:
                raise UserError(
                    _('Coach of employee %s is not set.') % employee.name)
            responsible = employee.coach_id.user_id
            if not responsible:
                raise UserError(
                    _('User of coach of employee %s is not set.') %
                    employee.name)
        elif self.responsible == 'manager':
            if not employee.parent_id:
                raise UserError(
                    _('Manager of employee %s is not set.') % employee.name)
            responsible = employee.parent_id.user_id
            if not responsible:
                raise UserError(
                    _('User of manager of employee %s is not set.') %
                    employee.name)
        elif self.responsible == 'employee':
            responsible = employee.user_id
            if not responsible:
                raise UserError(
                    _('User linked to employee %s is required.') %
                    employee.name)
        elif self.responsible == 'other':
            responsible = self.responsible_id
            if not responsible:
                raise UserError(
                    _('No specific user given on activity.') % employee.name)
        return responsible