示例#1
0
class product_template(osv.Model):
    _inherit = "product.template"

    _columns = {
        'website_description':
        fields.html('Description for the website'
                    ),  # hack, if website_sale is not installed
        'quote_description': fields.html('Description for the quote'),
    }
示例#2
0
    def test_00_sanitize(self):
        cr, uid, context = self.cr, self.uid, {}
        old_columns = self.partner._columns
        self.partner._columns = dict(old_columns)
        self.partner._columns.update({
            'comment': fields.html('Secure Html', sanitize=False),
        })
        some_ugly_html = """<p>Oops this should maybe be sanitized
% if object.some_field and not object.oriented:
<table>
    % if object.other_field:
    <tr style="border: 10px solid black;">
        ${object.mako_thing}
        <td>
    </tr>
    % endif
    <tr>
%if object.dummy_field:
        <p>Youpie</p>
%endif"""

        pid = self.partner.create(cr, uid, {
            'name': 'Raoul Poilvache',
            'comment': some_ugly_html,
        }, context=context)
        partner = self.partner.browse(cr, uid, pid, context=context)
        self.assertEqual(partner.comment, some_ugly_html, 'Error in HTML field: content was sanitized but field has sanitize=False')

        self.partner._columns.update({
            'comment': fields.html('Unsecure Html', sanitize=True),
        })
        self.partner.write(cr, uid, [pid], {
            'comment': some_ugly_html,
        }, context=context)
        partner = self.partner.browse(cr, uid, pid, context=context)
        # sanitize should have closed tags left open in the original html
        self.assertIn('</table>', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
        self.assertIn('</td>', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
        self.assertIn('<tr style="', partner.comment, 'Style attr should not have been stripped')

        self.partner._columns['comment'] = fields.html('Stripped Html', sanitize=True, strip_style=True)
        self.partner.write(cr, uid, [pid], {'comment': some_ugly_html}, context=context)
        partner = self.partner.browse(cr, uid, pid, context=context)
        self.assertNotIn('<tr style="', partner.comment, 'Style attr should have been stripped')

        self.partner._columns = old_columns
示例#3
0
class MassMailingList(osv.Model):
    """Model of a contact list. """
    _name = 'mail.mass_mailing.list'
    _order = 'name'
    _description = 'Mailing List'

    def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None):
        result = dict.fromkeys(ids, 0)
        Contacts = self.pool.get('mail.mass_mailing.contact')
        for group in Contacts.read_group(cr,
                                         uid, [('list_id', 'in', ids),
                                               ('opt_out', '!=', True)],
                                         ['list_id'], ['list_id'],
                                         context=context):
            result[group['list_id'][0]] = group['list_id_count']
        return result

    _columns = {
        'name':
        fields.char('Mailing List', required=True),
        'active':
        fields.boolean('Active'),
        'create_date':
        fields.datetime('Creation Date'),
        'contact_nbr':
        fields.function(
            _get_contact_nbr,
            type='integer',
            string='Number of Contacts',
        ),
        'popup_content':
        fields.html("Website Popup Content",
                    translate=True,
                    required=True,
                    sanitize=False),
        'popup_redirect_url':
        fields.char("Website Popup Redirect URL"),
    }

    def _get_default_popup_content(self, cr, uid, context=None):
        return """<div class="modal-header text-center">
    <h3 class="modal-title mt8">eCore 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>"""

    _defaults = {
        'active': True,
        'popup_content': _get_default_popup_content,
        'popup_redirect_url': '/',
    }
示例#4
0
class WebsiteResPartner(osv.Model):
    _name = 'res.partner'
    _inherit = [
        'res.partner', 'website.seo.metadata', 'website.published.mixin'
    ]

    def _get_ids(self, cr, uid, ids, flds, args, context=None):
        return {i: i for i in ids}

    def _set_private(self, cr, uid, ids, field_name, value, arg, context=None):
        return self.write(cr,
                          uid,
                          ids, {'website_published': not value},
                          context=context)

    def _get_private(self, cr, uid, ids, field_name, arg, context=None):
        return dict((rec.id, not rec.website_published)
                    for rec in self.browse(cr, uid, ids, context=context))

    def _search_private(self, cr, uid, obj, name, args, context=None):
        return [('website_published', '=', not args[0][2])]

    _columns = {
        'website_private':
        fields.function(_get_private,
                        fnct_inv=_set_private,
                        fnct_search=_search_private,
                        type='boolean',
                        string='Private Profile'),
        'website_description':
        fields.html('Website Partner Full Description', strip_style=True),
        'website_short_description':
        fields.text('Website Partner Short Description'),
        # hack to allow using plain browse record in qweb views
        'self':
        fields.function(_get_ids, type='many2one', relation=_name),
    }

    def _website_url(self, cr, uid, ids, field_name, arg, context=None):
        res = super(WebsiteResPartner, self)._website_url(cr,
                                                          uid,
                                                          ids,
                                                          field_name,
                                                          arg,
                                                          context=context)
        for partner in self.browse(cr, uid, ids, context=context):
            res[partner.id] = "/partners/%s" % slug(partner)
        return res

    _defaults = {
        'website_private': True,
    }
示例#5
0
文件: test_models.py 项目: ecoreos/hz
class test_converter(orm.Model):
    _name = 'web_editor.converter.test'

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

    _columns = {
        '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(),
    }
示例#6
0
class sale_order_line(osv.osv):
    _inherit = "sale.order.line"
    _description = "Sales Order Line"
    _columns = {
        'website_description':
        fields.html('Line Description'),
        'option_line_id':
        fields.one2many('sale.order.option', 'line_id',
                        'Optional Products Lines'),
    }

    def _inject_quote_description(self, cr, uid, values, context=None):
        values = dict(values or {})
        if not values.get('website_description') and values.get('product_id'):
            product = self.pool['product.product'].browse(cr,
                                                          uid,
                                                          values['product_id'],
                                                          context=context)
            values[
                'website_description'] = product.quote_description or product.website_description
        return values

    def create(self, cr, uid, values, context=None):
        values = self._inject_quote_description(cr, uid, values, context)
        ret = super(sale_order_line, self).create(cr,
                                                  uid,
                                                  values,
                                                  context=context)
        # hack because create don t make the job for a related field
        if values.get('website_description'):
            self.write(cr,
                       uid,
                       ret,
                       {'website_description': values['website_description']},
                       context=context)
        return ret

    def write(self, cr, uid, ids, values, context=None):
        values = self._inject_quote_description(cr, uid, values, context)
        return super(sale_order_line, self).write(cr,
                                                  uid,
                                                  ids,
                                                  values,
                                                  context=context)
示例#7
0
class sale_quote_template(osv.osv):
    _name = "sale.quote.template"
    _description = "Sale Quotation Template"
    _columns = {
        'name':
        fields.char('Quotation Template', required=True),
        'website_description':
        fields.html('Description', translate=True),
        'quote_line':
        fields.one2many('sale.quote.line',
                        'quote_id',
                        'Quotation Template Lines',
                        copy=True),
        'note':
        fields.text('Terms and conditions'),
        'options':
        fields.one2many('sale.quote.option',
                        'template_id',
                        'Optional Products Lines',
                        copy=True),
        'number_of_days':
        fields.integer(
            'Quotation Duration',
            help=
            'Number of days for the validity date computation of the quotation'
        ),
        'require_payment':
        fields.selection(
            [(0, 'Not mandatory on website quote validation'),
             (1, 'Immediate after website order validation')],
            'Payment',
            help=
            "Require immediate payment by the customer when validating the order from the website quote"
        ),
    }

    def open_template(self, cr, uid, quote_id, context=None):
        return {
            'type': 'ir.actions.act_url',
            'target': 'self',
            'url': '/quote/template/%d' % quote_id[0]
        }
示例#8
0
文件: hr_job.py 项目: ecoreos/hz
class hr_job(osv.osv):
    _name = 'hr.job'
    _inherit = ['hr.job', 'website.seo.metadata', 'website.published.mixin']

    def _website_url(self, cr, uid, ids, field_name, arg, context=None):
        res = super(hr_job, self)._website_url(cr,
                                               uid,
                                               ids,
                                               field_name,
                                               arg,
                                               context=context)
        for job in self.browse(cr, uid, ids, context=context):
            res[job.id] = "/jobs/detail/%s" % job.id
        return res

    def job_open(self, cr, uid, ids, context=None):
        self.write(cr, uid, ids, {'website_published': False}, context=context)
        return super(hr_job, self).job_open(cr, uid, ids, context)

    _columns = {
        'website_description': fields.html('Website description',
                                           translate=True),
    }
示例#9
0
    def test_00_sanitize(self):
        cr, uid, context = self.cr, self.uid, {}
        old_columns = self.partner._columns
        self.partner._columns = dict(old_columns)
        self.partner._columns.update({
            'comment':
            fields.html('Secure Html', sanitize=False),
        })
        some_ugly_html = """<p>Oops this should maybe be sanitized
% if object.some_field and not object.oriented:
<table>
    % if object.other_field:
    <tr style="border: 10px solid black;">
        ${object.mako_thing}
        <td>
    </tr>
    % endif
    <tr>
%if object.dummy_field:
        <p>Youpie</p>
%endif"""

        pid = self.partner.create(cr,
                                  uid, {
                                      'name': 'Raoul Poilvache',
                                      'comment': some_ugly_html,
                                  },
                                  context=context)
        partner = self.partner.browse(cr, uid, pid, context=context)
        self.assertEqual(
            partner.comment, some_ugly_html,
            'Error in HTML field: content was sanitized but field has sanitize=False'
        )

        self.partner._columns.update({
            'comment':
            fields.html('Unsecure Html', sanitize=True),
        })
        self.partner.write(cr,
                           uid, [pid], {
                               'comment': some_ugly_html,
                           },
                           context=context)
        partner = self.partner.browse(cr, uid, pid, context=context)
        # sanitize should have closed tags left open in the original html
        self.assertIn(
            '</table>', partner.comment,
            'Error in HTML field: content does not seem to have been sanitized despise sanitize=True'
        )
        self.assertIn(
            '</td>', partner.comment,
            'Error in HTML field: content does not seem to have been sanitized despise sanitize=True'
        )
        self.assertIn('<tr style="', partner.comment,
                      'Style attr should not have been stripped')

        self.partner._columns['comment'] = fields.html('Stripped Html',
                                                       sanitize=True,
                                                       strip_style=True)
        self.partner.write(cr,
                           uid, [pid], {'comment': some_ugly_html},
                           context=context)
        partner = self.partner.browse(cr, uid, pid, context=context)
        self.assertNotIn('<tr style="', partner.comment,
                         'Style attr should have been stripped')

        self.partner._columns = old_columns
示例#10
0
文件: models.py 项目: ecoreos/hz
class test_model(orm.Model):
    _name = 'test_converter.test_model'

    _columns = {
        'char':
        fields.char(),
        'integer':
        fields.integer(),
        'float':
        fields.float(),
        'numeric':
        fields.float(digits=(16, 2)),
        'many2one':
        fields.many2one('test_converter.test_model.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="Lorsqu'un pancake prend l'avion à destination de Toronto et "
            "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 `_group_by_full` functionality
    # test this feature here...

    def _gbf_m2o(self, cr, uid, ids, domain, read_group_order,
                 access_rights_uid, context):
        Sub = self.pool['test_converter.test_model.sub']
        all_ids = Sub._search(cr,
                              uid, [],
                              access_rights_uid=access_rights_uid,
                              context=context)
        result = Sub.name_get(cr,
                              access_rights_uid or uid,
                              all_ids,
                              context=context)
        folds = {i: i not in ids for i, _ in result}
        return result, folds

    _group_by_full = {
        'many2one': _gbf_m2o,
    }
示例#11
0
class sale_order_option(osv.osv):
    _name = "sale.order.option"
    _description = "Sale Options"
    _columns = {
        'order_id':
        fields.many2one('sale.order',
                        'Sale Order Reference',
                        ondelete='cascade',
                        select=True),
        'line_id':
        fields.many2one('sale.order.line', on_delete="set null"),
        'name':
        fields.text('Description', required=True),
        'product_id':
        fields.many2one('product.product',
                        'Product',
                        domain=[('sale_ok', '=', True)]),
        'website_description':
        fields.html('Line Description'),
        'price_unit':
        fields.float('Unit Price',
                     required=True,
                     digits_compute=dp.get_precision('Product Price')),
        'discount':
        fields.float('Discount (%)',
                     digits_compute=dp.get_precision('Discount')),
        'uom_id':
        fields.many2one('product.uom', 'Unit of Measure ', required=True),
        'quantity':
        fields.float('Quantity',
                     required=True,
                     digits_compute=dp.get_precision('Product UoS')),
    }

    _defaults = {
        'quantity': 1,
    }

    # TODO master: to remove, replaced by onchange of the new api
    def on_change_product_id(self,
                             cr,
                             uid,
                             ids,
                             product,
                             uom_id=None,
                             context=None):
        vals, domain = {}, []
        if not product:
            return vals
        product_obj = self.pool.get('product.product').browse(cr,
                                                              uid,
                                                              product,
                                                              context=context)
        name = product_obj.name
        if product_obj.description_sale:
            name += '\n' + product_obj.description_sale
        vals.update({
            'price_unit':
            product_obj.list_price,
            'website_description':
            product_obj and
            (product_obj.quote_description or product_obj.website_description),
            'name':
            name,
            'uom_id':
            uom_id or product_obj.uom_id.id,
        })
        uom_obj = self.pool.get('product.uom')
        if vals['uom_id'] != product_obj.uom_id.id:
            selected_uom = uom_obj.browse(cr,
                                          uid,
                                          vals['uom_id'],
                                          context=context)
            new_price = uom_obj._compute_price(cr, uid, product_obj.uom_id.id,
                                               vals['price_unit'],
                                               vals['uom_id'])
            vals['price_unit'] = new_price
        if not uom_id:
            domain = {
                'uom_id':
                [('category_id', '=', product_obj.uom_id.category_id.id)]
            }
        return {'value': vals, 'domain': domain}

    # TODO master: to remove, replaced by onchange of the new api
    def product_uom_change(self, cr, uid, ids, product, uom_id, context=None):
        context = context or {}
        if not uom_id:
            return {'value': {'price_unit': 0.0, 'uom_id': False}}
        return self.on_change_product_id(cr,
                                         uid,
                                         ids,
                                         product,
                                         uom_id=uom_id,
                                         context=context)

    @api.onchange('product_id', 'uom_id')
    def _onchange_product_id(self):
        if not self.product_id:
            return
        product = self.product_id.with_context(
            lang=self.order_id.partner_id.lang)
        self.price_unit = product.list_price
        self.website_description = product.quote_description or product.website_description
        self.name = product.name
        if product.description_sale:
            self.name += '\n' + product.description_sale
        self.uom_id = product.product_tmpl_id.uom_id
        if product and self.order_id.pricelist_id:
            partner_id = self.order_id.partner_id.id
            pricelist = self.order_id.pricelist_id.id
            self.price_unit = self.order_id.pricelist_id.price_get(
                product.id, self.quantity, partner_id)[pricelist]
        if self.uom_id and self.uom_id != self.product_id.uom_id:
            self.price_unit = self.product_id.uom_id._compute_price(
                self.price_unit, self.uom_id.id)
        domain = {
            'uom_id':
            [('category_id', '=', self.product_id.uom_id.category_id.id)]
        }
        return {'domain': domain}
示例#12
0
class sale_quote_option(osv.osv):
    _name = "sale.quote.option"
    _description = "Quotation Option"
    _columns = {
        'template_id':
        fields.many2one('sale.quote.template',
                        'Quotation Template Reference',
                        ondelete='cascade',
                        select=True,
                        required=True),
        'name':
        fields.text('Description', required=True, translate=True),
        'product_id':
        fields.many2one('product.product',
                        'Product',
                        domain=[('sale_ok', '=', True)],
                        required=True),
        'website_description':
        fields.html('Option Description', translate=True),
        'price_unit':
        fields.float('Unit Price',
                     required=True,
                     digits_compute=dp.get_precision('Product Price')),
        'discount':
        fields.float('Discount (%)',
                     digits_compute=dp.get_precision('Discount')),
        'uom_id':
        fields.many2one('product.uom', 'Unit of Measure ', required=True),
        'quantity':
        fields.float('Quantity',
                     required=True,
                     digits_compute=dp.get_precision('Product UoS')),
    }
    _defaults = {
        'quantity': 1,
    }

    def on_change_product_id(self,
                             cr,
                             uid,
                             ids,
                             product,
                             uom_id=None,
                             context=None):
        vals, domain = {}, []
        product_obj = self.pool.get('product.product').browse(cr,
                                                              uid,
                                                              product,
                                                              context=context)
        name = product_obj.name
        if product_obj.description_sale:
            name += '\n' + product_obj.description_sale
        vals.update({
            'price_unit': product_obj.list_price,
            'website_description':
            product_obj.product_tmpl_id.quote_description,
            'name': name,
            'uom_id': uom_id or product_obj.uom_id.id,
        })
        uom_obj = self.pool.get('product.uom')
        if vals['uom_id'] != product_obj.uom_id.id:
            selected_uom = uom_obj.browse(cr,
                                          uid,
                                          vals['uom_id'],
                                          context=context)
            new_price = uom_obj._compute_price(cr, uid, product_obj.uom_id.id,
                                               vals['price_unit'],
                                               vals['uom_id'])
            vals['price_unit'] = new_price
        if not uom_id:
            domain = {
                'uom_id':
                [('category_id', '=', product_obj.uom_id.category_id.id)]
            }
        return {'value': vals, 'domain': domain}

    def product_uom_change(self, cr, uid, ids, product, uom_id, context=None):
        if not uom_id:
            return {'value': {'price_unit': 0.0, 'uom_id': False}}
        return self.on_change_product_id(cr,
                                         uid,
                                         ids,
                                         product,
                                         uom_id=uom_id,
                                         context=context)
示例#13
0
class sale_order(osv.osv):
    _inherit = 'sale.order'

    def _get_total(self, cr, uid, ids, name, arg, context=None):
        res = {}
        for order in self.browse(cr, uid, ids, context=context):
            total = 0.0
            for line in order.order_line:
                total += line.price_subtotal + line.price_unit * (
                    (line.discount or 0.0) / 100.0) * line.product_uom_qty
            res[order.id] = total
        return res

    _columns = {
        'access_token':
        fields.char('Security Token', required=True, copy=False),
        'template_id':
        fields.many2one('sale.quote.template',
                        'Quotation Template',
                        readonly=True,
                        states={
                            'draft': [('readonly', False)],
                            'sent': [('readonly', False)]
                        }),
        'website_description':
        fields.html('Description', translate=True),
        'options':
        fields.one2many('sale.order.option',
                        'order_id',
                        'Optional Products Lines',
                        readonly=True,
                        states={
                            'draft': [('readonly', False)],
                            'sent': [('readonly', False)]
                        },
                        copy=True),
        'amount_undiscounted':
        fields.function(_get_total,
                        string='Amount Before Discount',
                        type="float",
                        digits=0),
        'quote_viewed':
        fields.boolean('Quotation Viewed'),
        'require_payment':
        fields.selection(
            [(0, 'Not mandatory on website quote validation'),
             (1, 'Immediate after website order validation')],
            'Payment',
            help=
            "Require immediate payment by the customer when validating the order from the website quote"
        ),
    }

    def _get_template_id(self, cr, uid, context=None):
        try:
            template_id = self.pool.get('ir.model.data').get_object_reference(
                cr, uid, 'website_quote', 'website_quote_template_default')[1]
        except ValueError:
            template_id = False
        return template_id

    _defaults = {
        'access_token': lambda self, cr, uid, ctx={}: str(uuid.uuid4()),
        'template_id': _get_template_id,
    }

    def open_quotation(self, cr, uid, quote_id, context=None):
        quote = self.browse(cr, uid, quote_id[0], context=context)
        self.write(cr,
                   uid,
                   quote_id[0], {'quote_viewed': True},
                   context=context)
        return {
            'type': 'ir.actions.act_url',
            'target': 'self',
            'url': '/quote/%s/%s' % (quote.id, quote.access_token)
        }

    def onchange_template_id(self,
                             cr,
                             uid,
                             ids,
                             template_id,
                             partner=False,
                             fiscal_position_id=False,
                             pricelist_id=False,
                             context=None):
        if not template_id:
            return {}

        if partner:
            context = dict(context or {})
            context['lang'] = self.pool['res.partner'].browse(
                cr, uid, partner, context).lang

        pricelist_obj = self.pool['product.pricelist']

        lines = [(5, )]
        quote_template = self.pool.get('sale.quote.template').browse(
            cr, uid, template_id, context=context)
        for line in quote_template.quote_line:
            res = self.pool.get('sale.order.line').product_id_change(
                cr, uid, False, False, line.product_id.id,
                line.product_uom_qty, line.product_uom_id.id,
                line.product_uom_qty,
                line.product_uom_id.id, line.name, partner, False, True,
                time.strftime('%Y-%m-%d'), False, fiscal_position_id, True,
                context)
            data = res.get('value', {})
            if pricelist_id:
                uom_context = context.copy()
                uom_context['uom'] = line.product_uom_id.id
                price = pricelist_obj.price_get(
                    cr,
                    uid, [pricelist_id],
                    line.product_id.id,
                    1,
                    context=uom_context)[pricelist_id]
            else:
                price = line.price_unit

            if 'tax_id' in data:
                data['tax_id'] = [(6, 0, data['tax_id'])]
            else:
                fpos = (fiscal_position_id
                        and self.pool['account.fiscal.position'].browse(
                            cr, uid, fiscal_position_id)) or False
                taxes = fpos.map_tax(
                    line.product_id.product_tmpl_id.taxes_id
                ).ids if fpos else line.product_id.product_tmpl_id.taxes_id.ids
                data['tax_id'] = [(6, 0, taxes)]
            data.update({
                'name': line.name,
                'price_unit': price,
                'discount': line.discount,
                'product_uom_qty': line.product_uom_qty,
                'product_id': line.product_id.id,
                'product_uom': line.product_uom_id.id,
                'website_description': line.website_description,
                'state': 'draft',
            })
            lines.append((0, 0, data))
        options = []
        for option in quote_template.options:
            if pricelist_id:
                uom_context = context.copy()
                uom_context['uom'] = option.uom_id.id
                price = pricelist_obj.price_get(
                    cr,
                    uid, [pricelist_id],
                    option.product_id.id,
                    1,
                    context=uom_context)[pricelist_id]
            else:
                price = option.price_unit
            options.append((0, 0, {
                'product_id': option.product_id.id,
                'name': option.name,
                'quantity': option.quantity,
                'uom_id': option.uom_id.id,
                'price_unit': price,
                'discount': option.discount,
                'website_description': option.website_description,
            }))
        date = False
        if quote_template.number_of_days > 0:
            date = (datetime.datetime.now() + datetime.timedelta(
                quote_template.number_of_days)).strftime("%Y-%m-%d")
        data = {
            'order_line': lines,
            'website_description': quote_template.website_description,
            'options': options,
            'validity_date': date,
            'require_payment': quote_template.require_payment
        }
        if quote_template.note:
            data['note'] = quote_template.note
        return {'value': data}

    def recommended_products(self, cr, uid, ids, context=None):
        order_line = self.browse(cr, uid, ids[0], context=context).order_line
        product_pool = self.pool.get('product.product')
        products = []
        for line in order_line:
            products += line.product_id.product_tmpl_id.recommended_products(
                context=context)
        return products

    def get_access_action(self, cr, uid, ids, context=None):
        """ Override method that generated the link to access the document. Instead
        of the classic form view, redirect to the online quote if exists. """
        quote = self.browse(cr, uid, ids[0], context=context)
        if not quote.template_id:
            return super(sale_order, self).get_access_action(cr,
                                                             uid,
                                                             ids,
                                                             context=context)
        return {
            'type': 'ir.actions.act_url',
            'url': '/quote/%s' % quote.id,
            'target': 'self',
            'res_id': quote.id,
        }

    def action_quotation_send(self, cr, uid, ids, context=None):
        action = super(sale_order, self).action_quotation_send(cr,
                                                               uid,
                                                               ids,
                                                               context=context)
        ir_model_data = self.pool.get('ir.model.data')
        quote_template_id = self.read(cr,
                                      uid,
                                      ids, ['template_id'],
                                      context=context)[0]['template_id']
        if quote_template_id:
            try:
                template_id = ir_model_data.get_object_reference(
                    cr, uid, 'website_quote', 'email_template_edi_sale')[1]
            except ValueError:
                pass
            else:
                action['context'].update({
                    'default_template_id': template_id,
                    'default_use_template': True
                })

        return action

    def _confirm_online_quote(self, cr, uid, order_id, tx, context=None):
        """ Payment callback: validate the order and write tx details in chatter """
        order = self.browse(cr, uid, order_id, context=context)

        # create draft invoice if transaction is ok
        if tx and tx.state == 'done':
            if order.state in ['draft', 'sent']:
                self.signal_workflow(cr,
                                     SUPERUSER_ID, [order.id],
                                     'manual_invoice',
                                     context=context)
            message = _('Order payed by %s. Transaction: %s. Amount: %s.') % (
                tx.partner_id.name, tx.acquirer_reference, tx.amount)
            self.message_post(cr,
                              uid,
                              order_id,
                              body=message,
                              type='comment',
                              subtype='mt_comment',
                              context=context)
            return True
        return False

    def create(self, cr, uid, values, context=None):
        if not values.get('template_id'):
            defaults = self.default_get(cr,
                                        uid, ['template_id'],
                                        context=context)
            template_values = self.onchange_template_id(
                cr,
                uid, [],
                defaults.get('template_id'),
                partner=values.get('partner_id'),
                fiscal_position_id=values.get('fiscal_position'),
                context=context).get('value', {})
            values = dict(template_values, **values)
        return super(sale_order, self).create(cr, uid, values, context=context)
示例#14
0
class PaymentAcquirer(osv.Model):
    """ Acquirer Model. Each specific acquirer can extend the model by adding
    its own fields, using the acquirer_name as a prefix for the new fields.
    Using the required_if_provider='<name>' attribute on fields it is possible
    to have required fields that depend on a specific acquirer.

    Each acquirer has a link to an ir.ui.view record that is a template of
    a button used to display the payment form. See examples in ``payment_ogone``
    and ``payment_paypal`` modules.

    Methods that should be added in an acquirer-specific implementation:

     - ``<name>_form_generate_values(self, cr, uid, id, reference, amount, currency,
       partner_id=False, partner_values=None, tx_custom_values=None, context=None)``:
       method that generates the values used to render the form button template.
     - ``<name>_get_form_action_url(self, cr, uid, id, context=None):``: method
       that returns the url of the button form. It is used for example in
       ecommerce application, if you want to post some data to the acquirer.
     - ``<name>_compute_fees(self, cr, uid, id, amount, currency_id, country_id,
       context=None)``: computed the fees of the acquirer, using generic fields
       defined on the acquirer model (see fields definition).

    Each acquirer should also define controllers to handle communication between
    eCore and the acquirer. It generally consists in return urls given to the
    button form and that the acquirer uses to send the customer back after the
    transaction, with transaction details given as a POST request.
    """
    _name = 'payment.acquirer'
    _description = 'Payment Acquirer'
    _order = 'sequence'

    def _get_providers(self, cr, uid, context=None):
        return []

    # indirection to ease inheritance
    _provider_selection = lambda self, *args, **kwargs: self._get_providers(*args, **kwargs)

    _columns = {
        'name': fields.char('Name', required=True, translate=True),
        'provider': fields.selection(_provider_selection, string='Provider', required=True),
        'company_id': fields.many2one('res.company', 'Company', required=True),
        'pre_msg': fields.html('Help Message', translate=True,
                               help='Message displayed to explain and help the payment process.'),
        'post_msg': fields.html('Thanks Message', help='Message displayed after having done the payment process.'),
        'view_template_id': fields.many2one('ir.ui.view', 'Form Button Template', required=True),
        'registration_view_template_id': fields.many2one('ir.ui.view', 'S2S Form Template',
                                                         domain=[('type', '=', 'qweb')],
                                                         help="Template for method registration"),
        'environment': fields.selection(
            [('test', 'Test'), ('prod', 'Production')],
            string='Environment', oldname='env'),
        'website_published': fields.boolean(
            'Visible in Portal / Website', copy=False,
            help="Make this payment acquirer available (Customer invoices, etc.)"),
        'auto_confirm': fields.selection(
            [('none', 'No automatic confirmation'),
             ('at_pay_confirm', 'At payment with acquirer confirmation'),
             ('at_pay_now', 'At payment no acquirer confirmation needed')],
            string='Order Confirmation', required=True),
        'pending_msg': fields.html('Pending Message', translate=True, help='Message displayed, if order is in pending state after having done the payment process.'),
        'done_msg': fields.html('Done Message', translate=True, help='Message displayed, if order is done successfully after having done the payment process.'),
        'cancel_msg': fields.html('Cancel Message', translate=True, help='Message displayed, if order is cancel during the payment process.'),
        'error_msg': fields.html('Error Message', translate=True, help='Message displayed, if error is occur during the payment process.'),
        # Fees
        'fees_active': fields.boolean('Add Extra Fees'),
        'fees_dom_fixed': fields.float('Fixed domestic fees'),
        'fees_dom_var': fields.float('Variable domestic fees (in percents)'),
        'fees_int_fixed': fields.float('Fixed international fees'),
        'fees_int_var': fields.float('Variable international fees (in percents)'),
        'sequence': fields.integer('Sequence', help="Determine the display order"),
    }

    image = ecore.fields.Binary("Image", attachment=True,
        help="This field holds the image used for this provider, limited to 1024x1024px")
    image_medium = ecore.fields.Binary("Medium-sized image",
        compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True,
        help="Medium-sized image of this provider. It is automatically "\
             "resized as a 128x128px image, with aspect ratio preserved. "\
             "Use this field in form views or some kanban views.")
    image_small = ecore.fields.Binary("Small-sized image",
        compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True,
        help="Small-sized image of this provider. It is automatically "\
             "resized as a 64x64px image, with aspect ratio preserved. "\
             "Use this field anywhere a small image is required.")

    @ecore.api.depends('image')
    def _compute_images(self):
        for rec in self:
            rec.image_medium = ecore.tools.image_resize_image_medium(rec.image)
            rec.image_small = ecore.tools.image_resize_image_small(rec.image)

    def _inverse_image_medium(self):
        for rec in self:
            rec.image = ecore.tools.image_resize_image_big(rec.image_medium)

    def _inverse_image_small(self):
        for rec in self:
            rec.image = ecore.tools.image_resize_image_big(rec.image_small)

    _defaults = {
        'company_id': lambda self, cr, uid, obj, ctx=None: self.pool['res.users'].browse(cr, uid, uid).company_id.id,
        'environment': 'prod',
        'website_published': False,
        'auto_confirm': 'at_pay_confirm',
        'pending_msg': '<i>Pending,</i> Your online payment has been successfully processed. But your order is not validated yet.',
        'done_msg': '<i>Done,</i> Your online payment has been successfully processed. Thank you for your order.',
        'cancel_msg': '<i>Cancel,</i> Your payment has been cancelled.',
        'error_msg': "<i>Error,</i> Please be aware that an error occurred during the transaction. The order has been confirmed but won't be paid. Don't hesitate to contact us if you have any questions on the status of your order."
    }

    def _check_required_if_provider(self, cr, uid, ids, context=None):
        """ If the field has 'required_if_provider="<provider>"' attribute, then it
        required if record.provider is <provider>. """
        for acquirer in self.browse(cr, uid, ids, context=context):
            if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()):
                return False
        return True

    _constraints = [
        (_check_required_if_provider, 'Required fields not filled', ['required for this provider']),
    ]

    def get_form_action_url(self, cr, uid, id, context=None):
        """ Returns the form action URL, for form-based acquirer implementations. """
        acquirer = self.browse(cr, uid, id, context=context)
        if hasattr(self, '%s_get_form_action_url' % acquirer.provider):
            return getattr(self, '%s_get_form_action_url' % acquirer.provider)(cr, uid, id, context=context)
        return False

    def render(self, cr, uid, id, reference, amount, currency_id, partner_id=False, values=None, context=None):
        """ Renders the form template of the given acquirer as a qWeb template.
        :param string reference: the transaction reference
        :param float amount: the amount the buyer has to pay
        :param currency_id: currency id
        :param dict partner_id: optional partner_id to fill values
        :param dict values: a dictionary of values for the transction that is
        given to the acquirer-specific method generating the form values
        :param dict context: eCore context

        All templates will receive:

         - acquirer: the payment.acquirer browse record
         - user: the current user browse record
         - currency_id: id of the transaction currency
         - amount: amount of the transaction
         - reference: reference of the transaction
         - partner_*: partner-related values
         - partner: optional partner browse record
         - 'feedback_url': feedback URL, controler that manage answer of the acquirer (without base url) -> FIXME
         - 'return_url': URL for coming back after payment validation (wihout base url) -> FIXME
         - 'cancel_url': URL if the client cancels the payment -> FIXME
         - 'error_url': URL if there is an issue with the payment -> FIXME
         - context: eCore context dictionary

        """
        if context is None:
            context = {}
        if values is None:
            values = {}
        acquirer = self.browse(cr, uid, id, context=context)

        # reference and amount
        values.setdefault('reference', reference)
        amount = float_round(amount, 2)
        values.setdefault('amount', amount)

        # currency id
        currency_id = values.setdefault('currency_id', currency_id)
        if currency_id:
            currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context)
        else:
            currency = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id
        values['currency'] = currency

        # Fill partner_* using values['partner_id'] or partner_id arguement
        partner_id = values.get('partner_id', partner_id)
        if partner_id:
            partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
            values.update({
                'partner': partner,
                'partner_id': partner_id,
                'partner_name': partner.name,
                'partner_lang': partner.lang,
                'partner_email': partner.email,
                'partner_zip': partner.zip,
                'partner_city': partner.city,
                'partner_address': _partner_format_address(partner.street, partner.street2),
                'partner_country_id': partner.country_id.id,
                'partner_country': partner.country_id,
                'partner_phone': partner.phone,
                'partner_state': partner.state_id,
            })
        if values.get('partner_name'):
            values.update({
                'partner_first_name': _partner_split_name(values.get('partner_name'))[0],
                'partner_last_name': _partner_split_name(values.get('partner_name'))[1],
            })

        # Fix address, country fields
        if not values.get('partner_address'):
            values['address'] = _partner_format_address(values.get('partner_street', ''), values.get('partner_street2', ''))
        if not values.get('partner_country') and values.get('partner_country_id'):
            values['country'] = self.pool['res.country'].browse(cr, uid, values.get('partner_country_id'), context=context)


        # compute fees
        fees_method_name = '%s_compute_fees' % acquirer.provider
        if hasattr(self, fees_method_name):
            fees = getattr(self, fees_method_name)(cr, uid, id, values['amount'], values['currency_id'], values['partner_country_id'], context=None)
            values['fees'] = float_round(fees, 2)

        # call <name>_form_generate_values to update the tx dict with acqurier specific values
        cust_method_name = '%s_form_generate_values' % (acquirer.provider)
        if hasattr(self, cust_method_name):
            method = getattr(self, cust_method_name)
            values = method(cr, uid, id, values, context=context)

        values.update({
            'tx_url': context.get('tx_url', self.get_form_action_url(cr, uid, id, context=context)),
            'submit_class': context.get('submit_class', 'btn btn-link'),
            'submit_txt': context.get('submit_txt'),
            'acquirer': acquirer,
            'user': self.pool.get("res.users").browse(cr, uid, uid, context=context),
            'context': context,
            'type': values.get('type') or 'form',
        })
        values.setdefault('return_url', False)

        # because render accepts view ids but not qweb -> need to use the xml_id
        return self.pool['ir.ui.view'].render(cr, uid, acquirer.view_template_id.xml_id, values, engine='ir.qweb', context=context)

    def _registration_render(self, cr, uid, id, partner_id, qweb_context=None, context=None):
        acquirer = self.browse(cr, uid, id, context=context)
        if qweb_context is None:
            qweb_context = {}
        qweb_context.update(id=id, partner_id=partner_id)
        method_name = '_%s_registration_form_generate_values' % (acquirer.provider,)
        if hasattr(self, method_name):
            method = getattr(self, method_name)
            qweb_context.update(method(cr, uid, id, qweb_context, context=context))
        return self.pool['ir.ui.view'].render(cr, uid, acquirer.registration_view_template_id.xml_id, qweb_context, engine='ir.qweb', context=context)

    def s2s_process(self, cr, uid, id, data, context=None):
        acquirer = self.browse(cr, uid, id, context=context)
        cust_method_name = '%s_s2s_form_process' % (acquirer.provider)
        if not self.s2s_validate(cr, uid, id, data, context=context):
            return False
        if hasattr(self, cust_method_name):
            method = getattr(self, cust_method_name)
            return method(cr, uid, data, context=context)
        return True

    def s2s_validate(self, cr, uid, id, data, context=None):
        acquirer = self.browse(cr, uid, id, context=context)
        cust_method_name = '%s_s2s_form_validate' % (acquirer.provider)
        if hasattr(self, cust_method_name):
            method = getattr(self, cust_method_name)
            return method(cr, uid, id, data, context=context)
        return True
示例#15
0
class crm_lead_forward_to_partner(osv.TransientModel):
    """ Forward info history to partners. """
    _name = 'crm.lead.forward.to.partner'

    def _convert_to_assignation_line(self, cr, uid, lead, partner, context=None):
        lead_location = []
        partner_location = []
        if lead.country_id:
            lead_location.append(lead.country_id.name)
        if lead.city:
            lead_location.append(lead.city)
        if partner:
            if partner.country_id:
                partner_location.append(partner.country_id.name)
            if partner.city:
                partner_location.append(partner.city)
        return {'lead_id': lead.id,
                'lead_location': ", ".join(lead_location),
                'partner_assigned_id': partner and partner.id or False,
                'partner_location': ", ".join(partner_location),
                'lead_link': self.get_lead_portal_url(cr, uid, lead.id, lead.type, context=context),
                }

    def default_get(self, cr, uid, fields, context=None):
        if context is None:
            context = {}
        lead_obj = self.pool.get('crm.lead')
        email_template_obj = self.pool.get('mail.template')
        try:
            template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm_partner_assign', 'email_template_lead_forward_mail')[1]
        except ValueError:
            template_id = False
        res = super(crm_lead_forward_to_partner, self).default_get(cr, uid, fields, context=context)
        active_ids = context.get('active_ids')
        default_composition_mode = context.get('default_composition_mode')
        res['assignation_lines'] = []
        if template_id:
            res['body'] = email_template_obj.get_email_template(cr, uid, template_id, 0).body_html
        if active_ids:
            lead_ids = lead_obj.browse(cr, uid, active_ids, context=context)
            if default_composition_mode == 'mass_mail':
                partner_assigned_ids = lead_obj.search_geo_partner(cr, uid, active_ids, context=context)
            else:
                partner_assigned_ids = dict((lead.id, lead.partner_assigned_id and lead.partner_assigned_id.id or False) for lead in lead_ids)
                res['partner_id'] = lead_ids[0].partner_assigned_id.id
            for lead in lead_ids:
                partner_id = partner_assigned_ids.get(lead.id) or False
                partner = False
                if partner_id:
                    partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
                res['assignation_lines'].append((0, 0, self._convert_to_assignation_line(cr, uid, lead, partner)))
        return res

    def action_forward(self, cr, uid, ids, context=None):
        lead_obj = self.pool.get('crm.lead')
        record = self.browse(cr, uid, ids[0], context=context)
        email_template_obj = self.pool.get('mail.template')
        try:
            template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm_partner_assign', 'email_template_lead_forward_mail')[1]
        except ValueError:
            raise UserError(_('The Forward Email Template is not in the database'))
        try:
            portal_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'group_portal')[1]
        except ValueError:
            raise UserError(_('The Portal group cannot be found'))

        local_context = context.copy()
        if not (record.forward_type == 'single'):
            no_email = set()
            for lead in record.assignation_lines:
                if lead.partner_assigned_id and not lead.partner_assigned_id.email:
                    no_email.add(lead.partner_assigned_id.name)
            if no_email:
                raise UserError(_('Set an email address for the partner(s): %s') % ", ".join(no_email))
        if record.forward_type == 'single' and not record.partner_id.email:
            raise UserError(_('Set an email address for the partner %s') % record.partner_id.name)

        partners_leads = {}
        for lead in record.assignation_lines:
            partner = record.forward_type == 'single' and record.partner_id or lead.partner_assigned_id
            lead_details = {
                'lead_link': lead.lead_link,
                'lead_id': lead.lead_id,
            }
            if partner:
                partner_leads = partners_leads.get(partner.id)
                if partner_leads:
                    partner_leads['leads'].append(lead_details)
                else:
                    partners_leads[partner.id] = {'partner': partner, 'leads': [lead_details]}
        stage_id = False
        if record.assignation_lines and record.assignation_lines[0].lead_id.type == 'lead':
            try:
                stage_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm_partner_assign', 'stage_portal_lead_assigned')[1]
            except ValueError:
                pass

        for partner_id, partner_leads in partners_leads.items():
            in_portal = False
            for contact in (partner.child_ids or [partner]):
                if contact.user_ids:
                    in_portal = portal_id in [g.id for g in contact.user_ids[0].groups_id]

            local_context['partner_id'] = partner_leads['partner']
            local_context['partner_leads'] = partner_leads['leads']
            local_context['partner_in_portal'] = in_portal
            email_template_obj.send_mail(cr, uid, template_id, ids[0], context=local_context)
            lead_ids = [lead['lead_id'].id for lead in partner_leads['leads']]
            values = {'partner_assigned_id': partner_id, 'user_id': partner_leads['partner'].user_id.id}
            if stage_id:
                values['stage_id'] = stage_id
            lead_obj.write(cr, uid, lead_ids, values)
            self.pool.get('crm.lead').message_subscribe(cr, uid, lead_ids, [partner_id], context=context)
        return True

    def get_lead_portal_url(self, cr, uid, lead_id, type, context=None):
        action = type == 'opportunity' and 'action_portal_opportunities' or 'action_portal_leads'
        try:
            action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm_partner_assign', action)[1]
        except ValueError:
            action_id = False
        portal_link = "%s/?db=%s#id=%s&action=%s&view_type=form" % (self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url'), cr.dbname, lead_id, action_id)
        return portal_link

    def get_portal_url(self, cr, uid, ids, context=None):
        portal_link = "%s/?db=%s" % (self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url'), cr.dbname)
        return portal_link

    _columns = {
        'forward_type': fields.selection([('single', 'a single partner: manual selection of partner'), ('assigned', "several partners: automatic assignation, using GPS coordinates and partner's grades"), ], 'Forward selected leads to'),
        'partner_id': fields.many2one('res.partner', 'Forward Leads To'),
        'assignation_lines': fields.one2many('crm.lead.assignation', 'forward_id', 'Partner Assignation'),
        'body': fields.html('Contents', help='Automatically sanitized HTML contents'),
    }

    _defaults = {
        'forward_type': lambda self, cr, uid, c: c.get('forward_type') or 'single',
    }
示例#16
0
class BlogPost(osv.Model):
    _name = "blog.post"
    _description = "Blog Post"
    _inherit = [
        'mail.thread', 'website.seo.metadata', 'website.published.mixin'
    ]
    _order = 'id DESC'
    _mail_post_access = 'read'

    def _website_url(self, cr, uid, ids, field_name, arg, context=None):
        res = super(BlogPost, self)._website_url(cr,
                                                 uid,
                                                 ids,
                                                 field_name,
                                                 arg,
                                                 context=context)
        for blog_post in self.browse(cr, uid, ids, context=context):
            res[blog_post.id] = "/blog/%s/post/%s" % (slug(
                blog_post.blog_id), slug(blog_post))
        return res

    def _compute_ranking(self, cr, uid, ids, name, arg, context=None):
        res = {}
        for blog_post in self.browse(cr, uid, ids, context=context):
            age = datetime.now() - datetime.strptime(
                blog_post.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
            res[blog_post.id] = blog_post.visits * (
                0.5 + random.random()) / max(3, age.days)
        return res

    def _default_content(self, cr, uid, context=None):
        return '''  <div class="container">
                        <section class="mt16 mb16">
                            <p class="o_default_snippet_text">''' + _(
            "Start writing here...") + '''</p>
                        </section>
                    </div> '''

    _columns = {
        'name':
        fields.char('Title', required=True, translate=True),
        'subtitle':
        fields.char('Sub Title', translate=True),
        'author_id':
        fields.many2one('res.partner', 'Author'),
        'cover_properties':
        fields.text('Cover Properties'),
        'blog_id':
        fields.many2one(
            'blog.blog',
            'Blog',
            required=True,
            ondelete='cascade',
        ),
        'tag_ids':
        fields.many2many(
            'blog.tag',
            string='Tags',
        ),
        'content':
        fields.html('Content', translate=True, sanitize=False),
        'website_message_ids':
        fields.one2many(
            'mail.message',
            'res_id',
            domain=lambda self: [
                '&', '&', ('model', '=', self._name),
                ('message_type', '=', 'comment'), ('path', '=', False)
            ],
            string='Website Messages',
            help="Website communication history",
        ),
        # creation / update stuff
        'create_date':
        fields.datetime(
            'Created on',
            select=True,
            readonly=True,
        ),
        'create_uid':
        fields.many2one(
            'res.users',
            'Author',
            select=True,
            readonly=True,
        ),
        'write_date':
        fields.datetime(
            'Last Modified on',
            select=True,
            readonly=True,
        ),
        'write_uid':
        fields.many2one(
            'res.users',
            'Last Contributor',
            select=True,
            readonly=True,
        ),
        'author_avatar':
        fields.related('author_id',
                       'image_small',
                       string="Avatar",
                       type="binary"),
        'visits':
        fields.integer('No of Views'),
        'ranking':
        fields.function(_compute_ranking, string='Ranking', type='float'),
    }

    _defaults = {
        'name':
        '',
        'content':
        _default_content,
        'cover_properties':
        '{"background-image": "none", "background-color": "oe_none", "opacity": "0.6", "resize_class": ""}',
        'author_id':
        lambda self, cr, uid, ctx=None: self.pool['res.users'].browse(
            cr, uid, uid, context=ctx).partner_id.id,
    }

    def html_tag_nodes(self, html, attribute=None, tags=None, context=None):
        """ Processing of html content to tag paragraphs and set them an unique
        ID.
        :return result: (html, mappin), where html is the updated html with ID
                        and mapping is a list of (old_ID, new_ID), where old_ID
                        is None is the paragraph is a new one. """

        existing_attributes = []
        mapping = []
        if not html:
            return html, mapping
        if tags is None:
            tags = ['p']
        if attribute is None:
            attribute = 'data-unique-id'

        # form a tree
        root = lxml.html.fragment_fromstring(html, create_parent='div')
        if not len(root) and root.text is None and root.tail is None:
            return html, mapping

        # check all nodes, replace :
        # - img src -> check URL
        # - a href -> check URL
        for node in root.iter():
            if node.tag not in tags:
                continue
            ancestor_tags = [parent.tag for parent in node.iterancestors()]

            old_attribute = node.get(attribute)
            new_attribute = old_attribute
            if not new_attribute or (old_attribute in existing_attributes):
                if ancestor_tags:
                    ancestor_tags.pop()
                counter = random.randint(10000, 99999)
                ancestor_tags.append('counter_%s' % counter)
                new_attribute = '/'.join(reversed(ancestor_tags))
                node.set(attribute, new_attribute)

            existing_attributes.append(new_attribute)
            mapping.append((old_attribute, new_attribute))

        html = lxml.html.tostring(root, pretty_print=False, method='html')
        # this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
        if html.startswith('<div>') and html.endswith('</div>'):
            html = html[5:-6]
        return html, mapping

    def _postproces_content(self, cr, uid, id, content=None, context=None):
        if content is None:
            content = self.browse(cr, uid, id, context=context).content
        if content is False:
            return content

        content, mapping = self.html_tag_nodes(content,
                                               attribute='data-chatter-id',
                                               tags=['p'],
                                               context=context)
        if id:  # not creating
            existing = [x[0] for x in mapping if x[0]]
            msg_ids = self.pool['mail.message'].search(
                cr,
                SUPERUSER_ID, [('res_id', '=', id), ('model', '=', self._name),
                               ('path', 'not in', existing),
                               ('path', '!=', False)],
                context=context)
            self.pool['mail.message'].unlink(cr,
                                             SUPERUSER_ID,
                                             msg_ids,
                                             context=context)

        return content

    def _check_for_publication(self, cr, uid, ids, vals, context=None):
        if vals.get('website_published'):
            base_url = self.pool['ir.config_parameter'].get_param(
                cr, uid, 'web.base.url')
            for post in self.browse(cr, uid, ids, context=context):
                post.blog_id.message_post(
                    body=
                    '<p>%(post_publication)s <a href="%(base_url)s/blog/%(blog_slug)s/post/%(post_slug)s">%(post_link)s</a></p>'
                    % {
                        'post_publication':
                        _('A new post %s has been published on the %s blog.') %
                        (post.name, post.blog_id.name),
                        'post_link':
                        _('Click here to access the post.'),
                        'base_url':
                        base_url,
                        'blog_slug':
                        slug(post.blog_id),
                        'post_slug':
                        slug(post),
                    },
                    subtype='website_blog.mt_blog_blog_published')
            return True
        return False

    def create(self, cr, uid, vals, context=None):
        if context is None:
            context = {}
        if 'content' in vals:
            vals['content'] = self._postproces_content(cr,
                                                       uid,
                                                       None,
                                                       vals['content'],
                                                       context=context)
        create_context = dict(context, mail_create_nolog=True)
        post_id = super(BlogPost, self).create(cr,
                                               uid,
                                               vals,
                                               context=create_context)
        self._check_for_publication(cr, uid, [post_id], vals, context=context)
        return post_id

    def write(self, cr, uid, ids, vals, context=None):
        if isinstance(ids, (int, long)):
            ids = [ids]
        if 'content' in vals:
            vals['content'] = self._postproces_content(cr,
                                                       uid,
                                                       ids[0],
                                                       vals['content'],
                                                       context=context)
        result = super(BlogPost, self).write(cr, uid, ids, vals, context)
        self._check_for_publication(cr, uid, ids, vals, context=context)
        return result

    def get_access_action(self, cr, uid, ids, context=None):
        """ Override method that generated the link to access the document. Instead
        of the classic form view, redirect to the post on the website directly """
        post = self.browse(cr, uid, ids[0], context=context)
        return {
            'type': 'ir.actions.act_url',
            'url': '/blog/%s/post/%s' % (post.blog_id.id, post.id),
            'target': 'self',
            'res_id': self.id,
        }

    def _notification_get_recipient_groups(self,
                                           cr,
                                           uid,
                                           ids,
                                           message,
                                           recipients,
                                           context=None):
        """ Override to set the access button: everyone can see an access button
        on their notification email. It will lead on the website view of the
        post. """
        res = super(BlogPost,
                    self)._notification_get_recipient_groups(cr,
                                                             uid,
                                                             ids,
                                                             message,
                                                             recipients,
                                                             context=context)
        access_action = self._notification_link_helper('view',
                                                       model=message.model,
                                                       res_id=message.res_id)
        for category, data in res.iteritems():
            res[category]['button_access'] = {
                'url': access_action,
                'title': _('View Blog Post')
            }
        return res
示例#17
0
文件: product.py 项目: ecoreos/hz
class product_template(osv.Model):
    _inherit = [
        "product.template", "website.seo.metadata", 'website.published.mixin',
        'rating.mixin'
    ]
    _order = 'website_published desc, website_sequence desc, name'
    _name = 'product.template'
    _mail_post_access = 'read'

    def _website_url(self, cr, uid, ids, field_name, arg, context=None):
        res = super(product_template, self)._website_url(cr,
                                                         uid,
                                                         ids,
                                                         field_name,
                                                         arg,
                                                         context=context)
        for product in self.browse(cr, uid, ids, context=context):
            res[product.id] = "/shop/product/%s" % (product.id, )
        return res

    _columns = {
        # TODO FIXME tde: when website_mail/mail_thread.py inheritance work -> this field won't be necessary
        'website_message_ids':
        fields.one2many(
            'mail.message',
            'res_id',
            domain=lambda self: [
                '&', ('model', '=', self._name),
                ('message_type', '=', 'comment')
            ],
            string='Website Comments',
        ),
        'website_description':
        fields.html('Description for the website',
                    sanitize=False,
                    translate=True),
        'alternative_product_ids':
        fields.many2many('product.template',
                         'product_alternative_rel',
                         'src_id',
                         'dest_id',
                         string='Suggested Products',
                         help='Appear on the product page'),
        'accessory_product_ids':
        fields.many2many('product.product',
                         'product_accessory_rel',
                         'src_id',
                         'dest_id',
                         string='Accessory Products',
                         help='Appear on the shopping cart'),
        'website_size_x':
        fields.integer('Size X'),
        'website_size_y':
        fields.integer('Size Y'),
        'website_style_ids':
        fields.many2many('product.style', string='Styles'),
        'website_sequence':
        fields.integer(
            'Sequence',
            help="Determine the display order in the Website E-commerce"),
        'public_categ_ids':
        fields.many2many(
            'product.public.category',
            string='Website Product Category',
            help=
            "Those categories are used to group similar products for e-commerce."
        ),
    }

    def _defaults_website_sequence(self, cr, uid, *l, **kwargs):
        cr.execute('SELECT MIN(website_sequence)-1 FROM product_template')
        next_sequence = cr.fetchone()[0] or 10
        return next_sequence

    _defaults = {
        'website_size_x': 1,
        'website_size_y': 1,
        'website_sequence': _defaults_website_sequence,
    }

    def set_sequence_top(self, cr, uid, ids, context=None):
        cr.execute('SELECT MAX(website_sequence) FROM product_template')
        max_sequence = cr.fetchone()[0] or 0
        return self.write(cr,
                          uid,
                          ids, {'website_sequence': max_sequence + 1},
                          context=context)

    def set_sequence_bottom(self, cr, uid, ids, context=None):
        cr.execute('SELECT MIN(website_sequence) FROM product_template')
        min_sequence = cr.fetchone()[0] or 0
        return self.write(cr,
                          uid,
                          ids, {'website_sequence': min_sequence - 1},
                          context=context)

    def set_sequence_up(self, cr, uid, ids, context=None):
        product = self.browse(cr, uid, ids[0], context=context)
        cr.execute("""  SELECT id, website_sequence FROM product_template
                        WHERE website_sequence > %s AND website_published = %s ORDER BY website_sequence ASC LIMIT 1"""
                   % (product.website_sequence, product.website_published))
        prev = cr.fetchone()
        if prev:
            self.write(cr,
                       uid, [prev[0]],
                       {'website_sequence': product.website_sequence},
                       context=context)
            return self.write(cr,
                              uid, [ids[0]], {'website_sequence': prev[1]},
                              context=context)
        else:
            return self.set_sequence_top(cr, uid, ids, context=context)

    def set_sequence_down(self, cr, uid, ids, context=None):
        product = self.browse(cr, uid, ids[0], context=context)
        cr.execute("""  SELECT id, website_sequence FROM product_template
                        WHERE website_sequence < %s AND website_published = %s ORDER BY website_sequence DESC LIMIT 1"""
                   % (product.website_sequence, product.website_published))
        next = cr.fetchone()
        if next:
            self.write(cr,
                       uid, [next[0]],
                       {'website_sequence': product.website_sequence},
                       context=context)
            return self.write(cr,
                              uid, [ids[0]], {'website_sequence': next[1]},
                              context=context)
        else:
            return self.set_sequence_bottom(cr, uid, ids, context=context)
示例#18
0
class MassMailing(osv.Model):
    """ MassMailing models a wave of emails for a mass mailign campaign.
    A mass mailing is an occurence of sending emails. """

    _name = 'mail.mass_mailing'
    _description = 'Mass Mailing'
    # number of periods for tracking mail_mail statistics
    _period_number = 6
    _order = 'sent_date DESC'
    # _send_trigger = 5  # Number under which mails are send directly

    _inherit = ['utm.mixin']

    def _get_statistics(self, cr, uid, ids, name, arg, context=None):
        """ Compute statistics of the mass mailing """
        results = {}
        cr.execute(
            """
            SELECT
                m.id as mailing_id,
                COUNT(s.id) AS total,
                COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent,
                COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null THEN 1 ELSE null END) AS scheduled,
                COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is not null THEN 1 ELSE null END) AS failed,
                COUNT(CASE WHEN s.sent is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered,
                COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened,
                COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied,
                COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced,
                COUNT(CASE WHEN s.exception is not null THEN 1 ELSE null END) AS failed
            FROM
                mail_mail_statistics s
            RIGHT JOIN
                mail_mass_mailing m
                ON (m.id = s.mass_mailing_id)
            WHERE
                m.id IN %s
            GROUP BY
                m.id
        """, (tuple(ids), ))
        for row in cr.dictfetchall():
            results[row.pop('mailing_id')] = row
            total = row['total'] or 1
            row['received_ratio'] = 100.0 * row['delivered'] / total
            row['opened_ratio'] = 100.0 * row['opened'] / total
            row['replied_ratio'] = 100.0 * row['replied'] / total
            row['bounced_ratio'] = 100.0 * row['bounced'] / total
        return results

    def _get_mailing_model(self, cr, uid, context=None):
        res = []
        for model_name in self.pool:
            model = self.pool[model_name]
            if hasattr(model, '_mail_mass_mailing') and getattr(
                    model, '_mail_mass_mailing'):
                res.append((model._name, getattr(model, '_mail_mass_mailing')))
        res.append(('mail.mass_mailing.contact', _('Mailing List')))
        return res

    def _get_clicks_ratio(self, cr, uid, ids, name, arg, context=None):
        res = dict.fromkeys(ids, 0)
        cr.execute(
            """
            SELECT COUNT(DISTINCT(stats.id)) AS nb_mails, COUNT(DISTINCT(clicks.mail_stat_id)) AS nb_clicks, stats.mass_mailing_id AS id 
            FROM mail_mail_statistics AS stats
            LEFT OUTER JOIN link_tracker_click AS clicks ON clicks.mail_stat_id = stats.id
            WHERE stats.mass_mailing_id IN %s
            GROUP BY stats.mass_mailing_id
        """, (tuple(ids), ))

        for record in cr.dictfetchall():
            res[record['id']] = 100 * record['nb_clicks'] / record['nb_mails']

        return res

    def _get_next_departure(self, cr, uid, ids, name, arg, context=None):
        mass_mailings = self.browse(cr, uid, ids, context=context)
        cron_next_call = self.pool.get('ir.model.data').xmlid_to_object(
            cr,
            SUPERUSER_ID,
            'mass_mailing.ir_cron_mass_mailing_queue',
            context=context).nextcall

        result = {}
        for mass_mailing in mass_mailings:
            schedule_date = mass_mailing.schedule_date
            if schedule_date:
                if datetime.now() > datetime.strptime(
                        schedule_date, tools.DEFAULT_SERVER_DATETIME_FORMAT):
                    result[mass_mailing.id] = cron_next_call
                else:
                    result[mass_mailing.id] = schedule_date
            else:
                result[mass_mailing.id] = cron_next_call
        return result

    def _get_total(self, cr, uid, ids, name, arg, context=None):
        mass_mailings = self.browse(cr, uid, ids, context=context)

        result = {}
        for mass_mailing in mass_mailings:
            mailing = self.browse(cr, uid, mass_mailing.id, context=context)
            result[mass_mailing.id] = len(
                self.get_recipients(cr, SUPERUSER_ID, mailing,
                                    context=context))
        return result

    # indirections for inheritance
    _mailing_model = lambda self, *args, **kwargs: self._get_mailing_model(
        *args, **kwargs)

    _columns = {
        'name':
        fields.char('Subject', required=True),
        'active':
        fields.boolean('Active'),
        'email_from':
        fields.char('From', required=True),
        'create_date':
        fields.datetime('Creation Date'),
        'sent_date':
        fields.datetime('Sent Date', oldname='date', copy=False),
        'schedule_date':
        fields.datetime('Schedule in the Future'),
        'body_html':
        fields.html('Body', translate=True),
        'attachment_ids':
        fields.many2many('ir.attachment', 'mass_mailing_ir_attachments_rel',
                         'mass_mailing_id', 'attachment_id', 'Attachments'),
        'keep_archives':
        fields.boolean('Keep Archives'),
        'mass_mailing_campaign_id':
        fields.many2one(
            'mail.mass_mailing.campaign',
            'Mass Mailing Campaign',
            ondelete='set null',
        ),
        'clicks_ratio':
        fields.function(
            _get_clicks_ratio,
            string="Number of Clicks",
            type="integer",
        ),
        'state':
        fields.selection([('draft', 'Draft'), ('in_queue', 'In Queue'),
                          ('sending', 'Sending'), ('done', 'Sent')],
                         string='Status',
                         required=True,
                         copy=False),
        'color':
        fields.related(
            'mass_mailing_campaign_id',
            'color',
            type='integer',
            string='Color Index',
        ),
        # mailing options
        'reply_to_mode':
        fields.selection(
            [('thread', 'Followers of leads/applicants'),
             ('email', 'Specified Email Address')],
            string='Reply-To Mode',
            required=True,
        ),
        'reply_to':
        fields.char('Reply To', help='Preferred Reply-To Address'),
        # recipients
        'mailing_model':
        fields.selection(_mailing_model,
                         string='Recipients Model',
                         required=True),
        'mailing_domain':
        fields.char('Domain', oldname='domain'),
        'contact_list_ids':
        fields.many2many(
            'mail.mass_mailing.list',
            'mail_mass_mailing_list_rel',
            string='Mailing Lists',
        ),
        'contact_ab_pc':
        fields.integer(
            'A/B Testing percentage',
            help=
            'Percentage of the contacts that will be mailed. Recipients will be taken randomly.'
        ),
        # statistics data
        'statistics_ids':
        fields.one2many(
            'mail.mail.statistics',
            'mass_mailing_id',
            'Emails Statistics',
        ),
        'total':
        fields.function(
            _get_total,
            string='Total',
            type='integer',
        ),
        'scheduled':
        fields.function(
            _get_statistics,
            string='Scheduled',
            type='integer',
            multi='_get_statistics',
        ),
        'failed':
        fields.function(
            _get_statistics,
            string='Failed',
            type='integer',
            multi='_get_statistics',
        ),
        'sent':
        fields.function(
            _get_statistics,
            string='Sent',
            type='integer',
            multi='_get_statistics',
        ),
        'delivered':
        fields.function(
            _get_statistics,
            string='Delivered',
            type='integer',
            multi='_get_statistics',
        ),
        'opened':
        fields.function(
            _get_statistics,
            string='Opened',
            type='integer',
            multi='_get_statistics',
        ),
        'replied':
        fields.function(
            _get_statistics,
            string='Replied',
            type='integer',
            multi='_get_statistics',
        ),
        'bounced':
        fields.function(
            _get_statistics,
            string='Bounced',
            type='integer',
            multi='_get_statistics',
        ),
        'failed':
        fields.function(
            _get_statistics,
            string='Failed',
            type='integer',
            multi='_get_statistics',
        ),
        'received_ratio':
        fields.function(
            _get_statistics,
            string='Received Ratio',
            type='integer',
            multi='_get_statistics',
        ),
        'opened_ratio':
        fields.function(
            _get_statistics,
            string='Opened Ratio',
            type='integer',
            multi='_get_statistics',
        ),
        'replied_ratio':
        fields.function(
            _get_statistics,
            string='Replied Ratio',
            type='integer',
            multi='_get_statistics',
        ),
        'bounced_ratio':
        fields.function(
            _get_statistics,
            String='Bouncded Ratio',
            type='integer',
            multi='_get_statistics',
        ),
        'next_departure':
        fields.function(_get_next_departure,
                        string='Next Departure',
                        type='datetime'),
    }

    def mass_mailing_statistics_action(self, cr, uid, ids, context=None):
        res = self.pool['ir.actions.act_window'].for_xml_id(
            cr,
            uid,
            'mass_mailing',
            'action_view_mass_mailing_statistics',
            context=context)
        link_click_ids = self.pool['link.tracker.click'].search(
            cr, uid, [('mass_mailing_id', 'in', ids)], context=context)
        res['domain'] = [('id', 'in', link_click_ids)]
        return res

    def default_get(self, cr, uid, fields, context=None):
        res = super(MassMailing, self).default_get(cr,
                                                   uid,
                                                   fields,
                                                   context=context)
        if 'reply_to_mode' in fields and not 'reply_to_mode' in res and res.get(
                'mailing_model'):
            if res['mailing_model'] in [
                    'res.partner', 'mail.mass_mailing.contact'
            ]:
                res['reply_to_mode'] = 'email'
            else:
                res['reply_to_mode'] = 'thread'
        return res

    _defaults = {
        'active':
        True,
        'state':
        'draft',
        'email_from':
        lambda self, cr, uid, ctx=None: self.pool[
            'mail.message']._get_default_from(cr, uid, context=ctx),
        'reply_to':
        lambda self, cr, uid, ctx=None: self.pool['mail.message'].
        _get_default_from(cr, uid, context=ctx),
        'mailing_model':
        'mail.mass_mailing.contact',
        'contact_ab_pc':
        100,
        'mailing_domain': [],
    }

    def onchange_mass_mailing_campaign_id(self,
                                          cr,
                                          uid,
                                          id,
                                          mass_mailing_campaign_ids,
                                          context=None):
        if mass_mailing_campaign_ids:
            mass_mailing_campaign = self.pool[
                'mail.mass_mailing.campaign'].browse(cr,
                                                     uid,
                                                     mass_mailing_campaign_ids,
                                                     context=context)

            dic = {
                'campaign_id': mass_mailing_campaign[0].campaign_id.id,
                'source_id': mass_mailing_campaign[0].source_id.id,
                'medium_id': mass_mailing_campaign[0].medium_id.id
            }
            return {'value': dic}

    #------------------------------------------------------
    # Technical stuff
    #------------------------------------------------------

    def copy_data(self, cr, uid, id, default=None, context=None):
        mailing = self.browse(cr, uid, id, context=context)
        default = dict(default or {}, name=_('%s (copy)') % mailing.name)
        return super(MassMailing, self).copy_data(cr,
                                                  uid,
                                                  id,
                                                  default,
                                                  context=context)

    def read_group(self,
                   cr,
                   uid,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   context=None,
                   orderby=False,
                   lazy=True):
        """ Override read_group to always display all states. """
        if groupby and groupby[0] == "state":
            # Default result structure
            # states = self._get_state_list(cr, uid, context=context)
            states = [('draft', 'Draft'), ('in_queue', 'In Queue'),
                      ('sending', 'Sending'), ('done', 'Sent')]
            read_group_all_states = [{
                '__context': {
                    'group_by': groupby[1:]
                },
                '__domain':
                domain + [('state', '=', state_value)],
                'state':
                state_value,
                'state_count':
                0,
            } for state_value, state_name in states]
            # Get standard results
            read_group_res = super(MassMailing,
                                   self).read_group(cr,
                                                    uid,
                                                    domain,
                                                    fields,
                                                    groupby,
                                                    offset=offset,
                                                    limit=limit,
                                                    context=context,
                                                    orderby=orderby)
            # Update standard results with default results
            result = []
            for state_value, state_name in states:
                res = filter(lambda x: x['state'] == state_value,
                             read_group_res)
                if not res:
                    res = filter(lambda x: x['state'] == state_value,
                                 read_group_all_states)
                res[0]['state'] = [state_value, state_name]
                result.append(res[0])
            return result
        else:
            return super(MassMailing, self).read_group(cr,
                                                       uid,
                                                       domain,
                                                       fields,
                                                       groupby,
                                                       offset=offset,
                                                       limit=limit,
                                                       context=context,
                                                       orderby=orderby)

    def update_opt_out(self,
                       cr,
                       uid,
                       mailing_id,
                       email,
                       res_ids,
                       value,
                       context=None):
        mailing = self.browse(cr, uid, mailing_id, context=context)
        model = self.pool[mailing.mailing_model]
        if 'opt_out' in model._fields:
            email_fname = 'email_from'
            if 'email' in model._fields:
                email_fname = 'email'
            record_ids = model.search(cr,
                                      uid, [('id', 'in', res_ids),
                                            (email_fname, 'ilike', email)],
                                      context=context)
            model.write(cr,
                        uid,
                        record_ids, {'opt_out': value},
                        context=context)

    #------------------------------------------------------
    # Views & Actions
    #------------------------------------------------------

    def on_change_model_and_list(self,
                                 cr,
                                 uid,
                                 ids,
                                 mailing_model,
                                 list_ids,
                                 context=None):
        value = {}
        if mailing_model == 'mail.mass_mailing.contact':
            mailing_list_ids = set()
            for item in list_ids:
                if isinstance(item, (int, long)):
                    mailing_list_ids.add(item)
                elif len(item) == 2 and item[0] == 4:  # 4, id
                    mailing_list_ids.add(item[1])
                elif len(item) == 3:  # 6, 0, ids
                    mailing_list_ids |= set(item[2])
            if mailing_list_ids:
                value[
                    'mailing_domain'] = "[('list_id', 'in', %s), ('opt_out', '=', False)]" % list(
                        mailing_list_ids)
            else:
                value['mailing_domain'] = "[('list_id', '=', False)]"
        else:
            value['mailing_domain'] = []
        value['body_html'] = "on_change_model_and_list"
        return {'value': value}

    def action_duplicate(self, cr, uid, ids, context=None):
        copy_id = None
        for mid in ids:
            copy_id = self.copy(cr, uid, mid, context=context)
        if copy_id:
            return {
                'type': 'ir.actions.act_window',
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'mail.mass_mailing',
                'res_id': copy_id,
                'context': context,
                'flags': {
                    'initial_mode': 'edit'
                },
            }
        return False

    def action_test_mailing(self, cr, uid, ids, context=None):
        ctx = dict(context, default_mass_mailing_id=ids[0])
        return {
            'name': _('Test Mailing'),
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_model': 'mail.mass_mailing.test',
            'target': 'new',
            'context': ctx,
        }

    #------------------------------------------------------
    # Email Sending
    #------------------------------------------------------

    def get_recipients(self, cr, uid, mailing, context=None):
        if mailing.mailing_domain:
            domain = eval(mailing.mailing_domain)
            res_ids = self.pool[mailing.mailing_model].search(cr,
                                                              uid,
                                                              domain,
                                                              context=context)
        else:
            res_ids = []
            domain = [('id', 'in', res_ids)]

        # randomly choose a fragment
        if mailing.contact_ab_pc < 100:
            contact_nbr = self.pool[mailing.mailing_model].search(
                cr, uid, domain, count=True, context=context)
            topick = int(contact_nbr / 100.0 * mailing.contact_ab_pc)
            if mailing.mass_mailing_campaign_id and mailing.mass_mailing_campaign_id.unique_ab_testing:
                already_mailed = self.pool[
                    'mail.mass_mailing.campaign'].get_recipients(
                        cr,
                        uid, [mailing.mass_mailing_campaign_id.id],
                        context=context)[mailing.mass_mailing_campaign_id.id]
            else:
                already_mailed = set([])
            remaining = set(res_ids).difference(already_mailed)
            if topick > len(remaining):
                topick = len(remaining)
            res_ids = random.sample(remaining, topick)
        return res_ids

    def get_remaining_recipients(self, cr, uid, mailing, context=None):
        res_ids = self.get_recipients(cr, uid, mailing, context=context)
        already_mailed = self.pool['mail.mail.statistics'].search_read(
            cr,
            uid, [('model', '=', mailing.mailing_model),
                  ('res_id', 'in', res_ids),
                  ('mass_mailing_id', '=', mailing.id)], ['res_id'],
            context=context)
        already_mailed_res_ids = [
            record['res_id'] for record in already_mailed
        ]
        return list(set(res_ids) - set(already_mailed_res_ids))

    def send_mail(self, cr, uid, ids, context=None):
        author_id = self.pool['res.users'].browse(
            cr, uid, uid, context=context).partner_id.id
        for mailing in self.browse(cr, uid, ids, context=context):
            # instantiate an email composer + send emails
            res_ids = self.get_remaining_recipients(cr,
                                                    uid,
                                                    mailing,
                                                    context=context)
            if not res_ids:
                raise UserError(_('Please select recipients.'))

            if context:
                comp_ctx = dict(context, active_ids=res_ids)
            else:
                comp_ctx = {'active_ids': res_ids}

            # Convert links in absolute URLs before the application of the shortener
            self.write(cr,
                       uid, [mailing.id], {
                           'body_html':
                           self.pool['mail.template']._replace_local_links(
                               cr, uid, mailing.body_html, context)
                       },
                       context=context)

            composer_values = {
                'author_id':
                author_id,
                'attachment_ids':
                [(4, attachment.id) for attachment in mailing.attachment_ids],
                'body':
                self.convert_links(cr, uid, [mailing.id],
                                   context=context)[mailing.id],
                'subject':
                mailing.name,
                'model':
                mailing.mailing_model,
                'email_from':
                mailing.email_from,
                'record_name':
                False,
                'composition_mode':
                'mass_mail',
                'mass_mailing_id':
                mailing.id,
                'mailing_list_ids':
                [(4, l.id) for l in mailing.contact_list_ids],
                'no_auto_thread':
                mailing.reply_to_mode != 'thread',
            }
            if mailing.reply_to_mode == 'email':
                composer_values['reply_to'] = mailing.reply_to

            composer_id = self.pool['mail.compose.message'].create(
                cr, uid, composer_values, context=comp_ctx)
            self.pool['mail.compose.message'].send_mail(cr,
                                                        uid, [composer_id],
                                                        auto_commit=True,
                                                        context=comp_ctx)
            self.write(cr,
                       uid, [mailing.id], {'state': 'done'},
                       context=context)
        return True

    def convert_links(self, cr, uid, ids, context=None):
        res = {}
        for mass_mailing in self.browse(cr, uid, ids, context=context):
            utm_mixin = mass_mailing.mass_mailing_campaign_id if mass_mailing.mass_mailing_campaign_id else mass_mailing
            html = mass_mailing.body_html if mass_mailing.body_html else ''

            vals = {'mass_mailing_id': mass_mailing.id}

            if mass_mailing.mass_mailing_campaign_id:
                vals[
                    'mass_mailing_campaign_id'] = mass_mailing.mass_mailing_campaign_id.id
            if utm_mixin.campaign_id:
                vals['campaign_id'] = utm_mixin.campaign_id.id
            if utm_mixin.source_id:
                vals['source_id'] = utm_mixin.source_id.id
            if utm_mixin.medium_id:
                vals['medium_id'] = utm_mixin.medium_id.id

            res[mass_mailing.id] = self.pool['link.tracker'].convert_links(
                cr,
                uid,
                html,
                vals,
                blacklist=['/unsubscribe_from_list'],
                context=context)

        return res

    def put_in_queue(self, cr, uid, ids, context=None):
        self.write(cr,
                   uid,
                   ids, {
                       'sent_date': fields.datetime.now(),
                       'state': 'in_queue'
                   },
                   context=context)

    def cancel_mass_mailing(self, cr, uid, ids, context=None):
        self.write(cr, uid, ids, {'state': 'draft'}, context=context)

    def retry_failed_mail(self, cr, uid, mass_mailing_ids, context=None):
        mail_mail_ids = self.pool.get('mail.mail').search(
            cr,
            uid, [('mailing_id', 'in', mass_mailing_ids),
                  ('state', '=', 'exception')],
            context=context)
        self.pool.get('mail.mail').unlink(cr,
                                          uid,
                                          mail_mail_ids,
                                          context=context)

        mail_mail_statistics_ids = self.pool.get(
            'mail.mail.statistics').search(
                cr, uid, [('mail_mail_id_int', 'in', mail_mail_ids)])
        self.pool.get('mail.mail.statistics').unlink(cr,
                                                     uid,
                                                     mail_mail_statistics_ids,
                                                     context=context)

        self.write(cr, uid, mass_mailing_ids, {'state': 'in_queue'})

    def _process_mass_mailing_queue(self, cr, uid, context=None):
        now = datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
        mass_mailing_ids = self.search(cr,
                                       uid, [('state', 'in',
                                              ('in_queue', 'sending')), '|',
                                             ('schedule_date', '<', now),
                                             ('schedule_date', '=', False)],
                                       context=context)

        for mass_mailing_id in mass_mailing_ids:
            mass_mailing_record = self.browse(cr,
                                              uid,
                                              mass_mailing_id,
                                              context=context)

            if len(
                    self.get_remaining_recipients(
                        cr, uid, mass_mailing_record, context=context)) > 0:
                self.write(cr,
                           uid, [mass_mailing_id], {'state': 'sending'},
                           context=context)
                self.send_mail(cr, uid, [mass_mailing_id], context=context)
            else:
                self.write(cr,
                           uid, [mass_mailing_id], {'state': 'done'},
                           context=context)
示例#19
0
文件: note.py 项目: LiberTang0/5
class note_note(osv.osv):
    """ Note """
    _name = 'note.note'
    _inherit = ['mail.thread']
    _description = "Note"

    #writing method (no modification of values)
    def name_create(self, cr, uid, name, context=None):
        rec_id = self.create(cr, uid, {'memo': name}, context=context)
        return self.name_get(cr, uid, [rec_id], context)[0]

    #read the first line (convert hml into text)
    def _get_note_first_line(self,
                             cr,
                             uid,
                             ids,
                             name="",
                             args={},
                             context=None):
        res = {}
        for note in self.browse(cr, uid, ids, context=context):
            res[note.id] = (note.memo and html2plaintext(note.memo)
                            or "").strip().replace('*', '').split("\n")[0]

        return res

    def onclick_note_is_done(self, cr, uid, ids, context=None):
        return self.write(cr,
                          uid,
                          ids, {
                              'open': False,
                              'date_done': fields.date.today()
                          },
                          context=context)

    def onclick_note_not_done(self, cr, uid, ids, context=None):
        return self.write(cr, uid, ids, {'open': True}, context=context)

    #return the default stage for the uid user
    def _get_default_stage_id(self, cr, uid, context=None):
        ids = self.pool.get('note.stage').search(cr,
                                                 uid, [('user_id', '=', uid)],
                                                 context=context)
        return ids and ids[0] or False

    def _set_stage_per_user(self,
                            cr,
                            uid,
                            id,
                            name,
                            value,
                            args=None,
                            context=None):
        note = self.browse(cr, uid, id, context=context)
        if not value: return False
        stage_ids = [value] + [
            stage.id for stage in note.stage_ids if stage.user_id.id != uid
        ]
        return self.write(cr,
                          uid, [id], {'stage_ids': [(6, 0, set(stage_ids))]},
                          context=context)

    def _get_stage_per_user(self, cr, uid, ids, name, args, context=None):
        result = dict.fromkeys(ids, False)
        for record in self.browse(cr, uid, ids, context=context):
            for stage in record.stage_ids:
                if stage.user_id.id == uid:
                    result[record.id] = stage.id
        return result

    _columns = {
        'name':
        fields.function(_get_note_first_line,
                        string='Note Summary',
                        type='text',
                        store=True),
        'user_id':
        fields.many2one('res.users', 'Owner'),
        'memo':
        fields.html('Note Content'),
        'sequence':
        fields.integer('Sequence'),
        'stage_id':
        fields.function(_get_stage_per_user,
                        fnct_inv=_set_stage_per_user,
                        string='Stage',
                        type='many2one',
                        relation='note.stage'),
        'stage_ids':
        fields.many2many('note.stage', 'note_stage_rel', 'note_id', 'stage_id',
                         'Stages of Users'),
        'open':
        fields.boolean('Active', track_visibility='onchange'),
        'date_done':
        fields.date('Date done'),
        'color':
        fields.integer('Color Index'),
        'tag_ids':
        fields.many2many('note.tag', 'note_tags_rel', 'note_id', 'tag_id',
                         'Tags'),
    }
    _defaults = {
        'user_id': lambda self, cr, uid, ctx=None: uid,
        'open': 1,
        'stage_id': _get_default_stage_id,
    }
    _order = 'sequence'

    def read_group(self,
                   cr,
                   uid,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   context=None,
                   orderby=False,
                   lazy=True):
        if groupby and groupby[0] == "stage_id":

            #search all stages
            current_stage_ids = self.pool.get('note.stage').search(
                cr, uid, [('user_id', '=', uid)], context=context)

            if current_stage_ids:  #if the user have some stages
                stages = self.pool['note.stage'].browse(cr,
                                                        uid,
                                                        current_stage_ids,
                                                        context=context)

                result = [{ #notes by stage for stages user
                        '__context': {'group_by': groupby[1:]},
                        '__domain': domain + [('stage_ids.id', '=', stage.id)],
                        'stage_id': (stage.id, stage.name),
                        'stage_id_count': self.search(cr,uid, domain+[('stage_ids', '=', stage.id)], context=context, count=True),
                        '__fold': stage.fold,
                    } for stage in stages]

                #note without user's stage
                nb_notes_ws = self.search(
                    cr,
                    uid,
                    domain + [('stage_ids', 'not in', current_stage_ids)],
                    context=context,
                    count=True)
                if nb_notes_ws:
                    # add note to the first column if it's the first stage
                    dom_not_in = ('stage_ids', 'not in', current_stage_ids)
                    if result and result[0]['stage_id'][
                            0] == current_stage_ids[0]:
                        dom_in = result[0]['__domain'].pop()
                        result[0]['__domain'] = domain + [
                            '|', dom_in, dom_not_in
                        ]
                        result[0]['stage_id_count'] += nb_notes_ws
                    else:
                        # add the first stage column
                        result = [{
                            '__context': {
                                'group_by': groupby[1:]
                            },
                            '__domain': domain + [dom_not_in],
                            'stage_id': (stages[0].id, stages[0].name),
                            'stage_id_count': nb_notes_ws,
                            '__fold': stages[0].name,
                        }] + result

            else:  # if stage_ids is empty

                #note without user's stage
                nb_notes_ws = self.search(cr,
                                          uid,
                                          domain,
                                          context=context,
                                          count=True)
                if nb_notes_ws:
                    result = [{  #notes for unknown stage
                        '__context': {
                            'group_by': groupby[1:]
                        },
                        '__domain': domain,
                        'stage_id': False,
                        'stage_id_count': nb_notes_ws
                    }]
                else:
                    result = []
            return result

        else:
            return super(note_note, self).read_group(cr,
                                                     uid,
                                                     domain,
                                                     fields,
                                                     groupby,
                                                     offset=offset,
                                                     limit=limit,
                                                     context=context,
                                                     orderby=orderby,
                                                     lazy=lazy)

    def _notification_get_recipient_groups(self,
                                           cr,
                                           uid,
                                           ids,
                                           message,
                                           recipients,
                                           context=None):
        res = super(note_note,
                    self)._notification_get_recipient_groups(cr,
                                                             uid,
                                                             ids,
                                                             message,
                                                             recipients,
                                                             context=context)
        new_action_id = self.pool['ir.model.data'].xmlid_to_res_id(
            cr, uid, 'note.action_note_note')
        new_action = self._notification_link_helper(cr,
                                                    uid,
                                                    ids,
                                                    'new',
                                                    context=context,
                                                    action_id=new_action_id)
        res['user'] = {
            'actions': [{
                'url': new_action,
                'title': _('New Note')
            }]
        }
        return res
示例#20
0
文件: res_users.py 项目: LiberTang0/5
class res_users(osv.osv):
    """ User class. A res.users record models an eCore user and is different
        from an employee.

        res.users class now inherits from res.partner. The partner model is
        used to store the data related to the partner: lang, name, address,
        avatar, ... The user model is now dedicated to technical data.
    """
    __uid_cache = {}
    _inherits = {
        'res.partner': 'partner_id',
    }
    _name = "res.users"
    _description = 'Users'
    _order = 'name, login'

    def _set_new_password(self, cr, uid, id, name, value, args, context=None):
        if value is False:
            # Do not update the password if no value is provided, ignore silently.
            # For example web client submits False values for all empty fields.
            return
        if uid == id:
            # To change their own password users must use the client-specific change password wizard,
            # so that the new password is immediately used for further RPC requests, otherwise the user
            # will face unexpected 'Access Denied' exceptions.
            raise UserError(_('Please use the change password wizard (in User Preferences or User menu) to change your own password.'))
        self.write(cr, uid, id, {'password': value})

    def _get_password(self, cr, uid, ids, arg, karg, context=None):
        return dict.fromkeys(ids, '')

    def _is_share(self, cr, uid, ids, name, args, context=None):
        res = {}
        for user in self.browse(cr, uid, ids, context=context):
            res[user.id] = not self.has_group(cr, user.id, 'base.group_user')
        return res

    def _store_trigger_share_res_groups(self, cr, uid, ids, context=None):
        group_user = self.pool['ir.model.data'].xmlid_to_object(cr, SUPERUSER_ID, 'base.group_user', context=context)
        if group_user and group_user.id in ids:
            return group_user.users.ids
        return []

    def _get_users_from_group(self, cr, uid, ids, context=None):
        result = set()
        groups = self.pool['res.groups'].browse(cr, uid, ids, context=context)
        # Clear cache to avoid perf degradation on databases with thousands of users
        groups.invalidate_cache()
        for group in groups:
            result.update(user.id for user in group.users)
        return list(result)

    _columns = {
        'id': fields.integer('ID'),
        'partner_id': fields.many2one('res.partner', required=True,
            string='Related Partner', ondelete='restrict',
            help='Partner-related data of the user', auto_join=True),
        'login': fields.char('Login', size=64, required=True,
            help="Used to log into the system"),
        'password': fields.char('Password', size=64, invisible=True, copy=False,
            help="Keep empty if you don't want the user to be able to connect on the system."),
        'new_password': fields.function(_get_password, type='char', size=64,
            fnct_inv=_set_new_password, string='Set Password',
            help="Specify a value only when creating a user or if you're "\
                 "changing the user's password, otherwise leave empty. After "\
                 "a change of password, the user has to login again."),
        'signature': fields.html('Signature'),
        'active': fields.boolean('Active'),
        'action_id': fields.many2one('ir.actions.actions', 'Home Action', help="If specified, this action will be opened at log on for this user, in addition to the standard menu."),
        'groups_id': fields.many2many('res.groups', 'res_groups_users_rel', 'uid', 'gid', 'Groups'),
        # Special behavior for this field: res.company.search() will only return the companies
        # available to the current user (should be the user's companies?), when the user_preference
        # context is set.
        'company_id': fields.many2one('res.company', 'Company', required=True,
            help='The company this user is currently working for.', context={'user_preference': True}),
        'company_ids':fields.many2many('res.company','res_company_users_rel','user_id','cid','Companies'),
        'share': fields.function(_is_share, string='Share User', type='boolean',
             store={
                 'res.users': (lambda self, cr, uid, ids, c={}: ids, ['groups_id'], 50),
                 'res.groups': (_store_trigger_share_res_groups, ['users'], 50),
             }, help="External user with limited access, created only for the purpose of sharing data."),
    }

    # overridden inherited fields to bypass access rights, in case you have
    # access to the user but not its corresponding partner
    name = ecore.fields.Char(related='partner_id.name', inherited=True)
    email = ecore.fields.Char(related='partner_id.email', inherited=True)
    log_ids = ecore.fields.One2many('res.users.log', 'create_uid', string='User log entries')
    login_date = ecore.fields.Datetime(related='log_ids.create_date', string='Latest connection')

    def on_change_login(self, cr, uid, ids, login, context=None):
        if login and tools.single_email_re.match(login):
            return {'value': {'email': login}}
        return {}

    def onchange_state(self, cr, uid, ids, state_id, context=None):
        partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
        return self.pool.get('res.partner').onchange_state(cr, uid, partner_ids, state_id, context=context)

    def onchange_parent_id(self, cr, uid, ids, parent_id, context=None):
        """ Wrapper on the user.partner onchange_address, because some calls to the
            partner form view applied to the user may trigger the
            partner.onchange_type method, but applied to the user object.
        """
        partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
        return self.pool['res.partner'].onchange_address(cr, uid, partner_ids, parent_id, context=context)

    def _check_company(self, cr, uid, ids, context=None):
        return all(((this.company_id in this.company_ids) or not this.company_ids) for this in self.browse(cr, uid, ids, context))

    _constraints = [
        (_check_company, 'The chosen company is not in the allowed companies for this user', ['company_id', 'company_ids']),
    ]

    _sql_constraints = [
        ('login_key', 'UNIQUE (login)',  'You can not have two users with the same login !')
    ]

    def _get_company(self,cr, uid, context=None, uid2=False):
        if not uid2:
            uid2 = uid
        # Use read() to compute default company, and pass load=_classic_write to
        # avoid useless name_get() calls. This will avoid prefetching fields
        # while computing default values for new db columns, as the
        # db backend may not be fully initialized yet.
        user_data = self.pool['res.users'].read(cr, uid, uid2, ['company_id'],
                                                context=context, load='_classic_write')
        comp_id = user_data['company_id']
        return comp_id or False

    def _get_companies(self, cr, uid, context=None):
        c = self._get_company(cr, uid, context)
        if c:
            return [c]
        return False

    def _get_group(self,cr, uid, context=None):
        dataobj = self.pool.get('ir.model.data')
        result = []
        try:
            dummy,group_id = dataobj.get_object_reference(cr, SUPERUSER_ID, 'base', 'group_user')
            result.append(group_id)
            dummy,group_id = dataobj.get_object_reference(cr, SUPERUSER_ID, 'base', 'group_partner_manager')
            result.append(group_id)
        except ValueError:
            # If these groups does not exists anymore
            pass
        return result

    _defaults = {
        'password': '',
        'active': True,
        'customer': False,
        'company_id': _get_company,
        'company_ids': _get_companies,
        'groups_id': _get_group,
    }

    # User can write on a few of his own fields (but not his groups for example)
    SELF_WRITEABLE_FIELDS = ['password', 'signature', 'action_id', 'company_id', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz']
    # User can read a few of his own fields
    SELF_READABLE_FIELDS = ['signature', 'company_id', 'login', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz', 'tz_offset', 'groups_id', 'partner_id', '__last_update', 'action_id']

    def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
        def override_password(o):
            if ('id' not in o or o['id'] != uid):
                for f in USER_PRIVATE_FIELDS:
                    if f in o:
                        o[f] = '********'
            return o

        if fields and (ids == [uid] or ids == uid):
            for key in fields:
                if not (key in self.SELF_READABLE_FIELDS or key.startswith('context_')):
                    break
            else:
                # safe fields only, so we read as super-user to bypass access rights
                uid = SUPERUSER_ID

        result = super(res_users, self).read(cr, uid, ids, fields=fields, context=context, load=load)
        canwrite = self.pool['ir.model.access'].check(cr, uid, 'res.users', 'write', False)
        if not canwrite:
            if isinstance(ids, (int, long)):
                result = override_password(result)
            else:
                result = map(override_password, result)

        return result

    def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True):
        if uid != SUPERUSER_ID:
            groupby_fields = set([groupby] if isinstance(groupby, basestring) else groupby)
            if groupby_fields.intersection(USER_PRIVATE_FIELDS):
                raise ecore.exceptions.AccessError('Invalid groupby')
        return super(res_users, self).read_group(
            cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby, lazy=lazy)

    def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
        if user != SUPERUSER_ID and args:
            domain_terms = [term for term in args if isinstance(term, (tuple, list))]
            domain_fields = set(left for (left, op, right) in domain_terms)
            if domain_fields.intersection(USER_PRIVATE_FIELDS):
                raise ecore.exceptions.AccessError('Invalid search criterion')
        return super(res_users, self)._search(
            cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count,
            access_rights_uid=access_rights_uid)

    def create(self, cr, uid, vals, context=None):
        user_id = super(res_users, self).create(cr, uid, vals, context=context)
        user = self.browse(cr, uid, user_id, context=context)
        if user.partner_id.company_id: 
            user.partner_id.write({'company_id': user.company_id.id})
        return user_id

    def write(self, cr, uid, ids, values, context=None):
        if not hasattr(ids, '__iter__'):
            ids = [ids]

        if values.get('active') == False:
            for current_id in ids:
                if current_id == SUPERUSER_ID:
                    raise UserError(_("You cannot unactivate the admin user."))
                elif current_id == uid:
                    raise UserError(_("You cannot unactivate the user you're currently logged in as."))

        if ids == [uid]:
            for key in values.keys():
                if not (key in self.SELF_WRITEABLE_FIELDS or key.startswith('context_')):
                    break
            else:
                if 'company_id' in values:
                    user = self.browse(cr, SUPERUSER_ID, uid, context=context)
                    if not (values['company_id'] in user.company_ids.ids):
                        del values['company_id']
                uid = 1 # safe fields only, so we write as super-user to bypass access rights

        res = super(res_users, self).write(cr, uid, ids, values, context=context)
        if 'company_id' in values:
            for user in self.browse(cr, uid, ids, context=context):
                # if partner is global we keep it that way
                if user.partner_id.company_id and user.partner_id.company_id.id != values['company_id']: 
                    user.partner_id.write({'company_id': user.company_id.id})
            # clear default ir values when company changes
            self.pool['ir.values'].get_defaults_dict.clear_cache(self.pool['ir.values'])
        # clear caches linked to the users
        self.pool['ir.model.access'].call_cache_clearing_methods(cr)
        clear = partial(self.pool['ir.rule'].clear_cache, cr)
        map(clear, ids)
        db = cr.dbname
        if db in self.__uid_cache:
            for id in ids:
                if id in self.__uid_cache[db]:
                    del self.__uid_cache[db][id]
        self.context_get.clear_cache(self)
        self.has_group.clear_cache(self)
        return res

    def unlink(self, cr, uid, ids, context=None):
        if 1 in ids:
            raise UserError(_('You can not remove the admin user as it is used internally for resources created by eCore (updates, module installation, ...)'))
        db = cr.dbname
        if db in self.__uid_cache:
            for id in ids:
                if id in self.__uid_cache[db]:
                    del self.__uid_cache[db][id]
        return super(res_users, self).unlink(cr, uid, ids, context=context)

    def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
        if not args:
            args=[]
        if not context:
            context={}
        ids = []
        if name and operator in ['=', 'ilike']:
            ids = self.search(cr, user, [('login','=',name)]+ args, limit=limit, context=context)
        if not ids:
            ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
        return self.name_get(cr, user, ids, context=context)

    def copy(self, cr, uid, id, default=None, context=None):
        user2copy = self.read(cr, uid, [id], ['login','name'])[0]
        default = dict(default or {})
        if ('name' not in default) and ('partner_id' not in default):
            default['name'] = _("%s (copy)") % user2copy['name']
        if 'login' not in default:
            default['login'] = _("%s (copy)") % user2copy['login']
        return super(res_users, self).copy(cr, uid, id, default, context)

    @tools.ormcache('uid')
    def context_get(self, cr, uid, context=None):
        user = self.browse(cr, SUPERUSER_ID, uid, context)
        result = {}
        for k in self._fields:
            if k.startswith('context_'):
                context_key = k[8:]
            elif k in ['lang', 'tz']:
                context_key = k
            else:
                context_key = False
            if context_key:
                res = getattr(user, k) or False
                if isinstance(res, models.BaseModel):
                    res = res.id
                result[context_key] = res or False
        return result

    def action_get(self, cr, uid, context=None):
        dataobj = self.pool['ir.model.data']
        data_id = dataobj._get_id(cr, SUPERUSER_ID, 'base', 'action_res_users_my')
        return dataobj.browse(cr, uid, data_id, context=context).res_id

    def check_super(self, passwd):
        return check_super(passwd)

    def check_credentials(self, cr, uid, password):
        """ Override this method to plug additional authentication methods"""
        res = self.search(cr, SUPERUSER_ID, [('id','=',uid),('password','=',password)])
        if not res:
            raise ecore.exceptions.AccessDenied()

    def _update_last_login(self, cr, uid):
        # only create new records to avoid any side-effect on concurrent transactions
        # extra records will be deleted by the periodical garbage collection
        self.pool['res.users.log'].create(cr, uid, {}) # populated by defaults

    def _login(self, db, login, password):
        if not password:
            return False
        user_id = False
        try:
            with self.pool.cursor() as cr:
                res = self.search(cr, SUPERUSER_ID, [('login','=',login)])
                if res:
                    user_id = res[0]
                    self.check_credentials(cr, user_id, password)
                    self._update_last_login(cr, user_id)
        except ecore.exceptions.AccessDenied:
            _logger.info("Login failed for db:%s login:%s", db, login)
            user_id = False
        return user_id

    def authenticate(self, db, login, password, user_agent_env):
        """Verifies and returns the user ID corresponding to the given
          ``login`` and ``password`` combination, or False if there was
          no matching user.

           :param str db: the database on which user is trying to authenticate
           :param str login: username
           :param str password: user password
           :param dict user_agent_env: environment dictionary describing any
               relevant environment attributes
        """
        uid = self._login(db, login, password)
        if uid == ecore.SUPERUSER_ID:
            # Successfully logged in as admin!
            # Attempt to guess the web base url...
            if user_agent_env and user_agent_env.get('base_location'):
                cr = self.pool.cursor()
                try:
                    base = user_agent_env['base_location']
                    ICP = self.pool['ir.config_parameter']
                    if not ICP.get_param(cr, uid, 'web.base.url.freeze'):
                        ICP.set_param(cr, uid, 'web.base.url', base)
                    cr.commit()
                except Exception:
                    _logger.exception("Failed to update web.base.url configuration parameter")
                finally:
                    cr.close()
        return uid

    def check(self, db, uid, passwd):
        """Verifies that the given (uid, password) is authorized for the database ``db`` and
           raise an exception if it is not."""
        if not passwd:
            # empty passwords disallowed for obvious security reasons
            raise ecore.exceptions.AccessDenied()
        if self.__uid_cache.setdefault(db, {}).get(uid) == passwd:
            return
        cr = self.pool.cursor()
        try:
            self.check_credentials(cr, uid, passwd)
            self.__uid_cache[db][uid] = passwd
        finally:
            cr.close()

    def change_password(self, cr, uid, old_passwd, new_passwd, context=None):
        """Change current user password. Old password must be provided explicitly
        to prevent hijacking an existing user session, or for cases where the cleartext
        password is not used to authenticate requests.

        :return: True
        :raise: ecore.exceptions.AccessDenied when old password is wrong
        :raise: except_osv when new password is not set or empty
        """
        self.check(cr.dbname, uid, old_passwd)
        if new_passwd:
            return self.write(cr, uid, uid, {'password': new_passwd})
        raise UserError(_("Setting empty passwords is not allowed for security reasons!"))

    def preference_save(self, cr, uid, ids, context=None):
        return {
            'type': 'ir.actions.client',
            'tag': 'reload_context',
        }

    def preference_change_password(self, cr, uid, ids, context=None):
        return {
            'type': 'ir.actions.client',
            'tag': 'change_password',
            'target': 'new',
        }

    @tools.ormcache('uid', 'group_ext_id')
    def has_group(self, cr, uid, group_ext_id):
        """Checks whether user belongs to given group.

        :param str group_ext_id: external ID (XML ID) of the group.
           Must be provided in fully-qualified form (``module.ext_id``), as there
           is no implicit module to use..
        :return: True if the current user is a member of the group with the
           given external ID (XML ID), else False.
        """
        assert group_ext_id and '.' in group_ext_id, "External ID must be fully qualified"
        module, ext_id = group_ext_id.split('.')
        cr.execute("""SELECT 1 FROM res_groups_users_rel WHERE uid=%s AND gid IN
                        (SELECT res_id FROM ir_model_data WHERE module=%s AND name=%s)""",
                   (uid, module, ext_id))
        return bool(cr.fetchone())

    @api.multi
    def _is_admin(self):
        return self.id == ecore.SUPERUSER_ID or self.sudo(self).has_group('base.group_erp_manager')

    def get_company_currency_id(self, cr, uid, context=None):
        return self.browse(cr, uid, uid, context=context).company_id.currency_id.id