diff options
| -rw-r--r-- | addons/event/models/event.py | 7 | ||||
| -rw-r--r-- | addons/l10n_nl/data/account_tax_template.xml | 4 | ||||
| -rw-r--r-- | addons/link_tracker/models/link_tracker.py | 13 | ||||
| -rw-r--r-- | addons/payment_ogone/controllers/main.py | 2 | ||||
| -rw-r--r-- | addons/sale/models/sale.py | 2 | ||||
| -rw-r--r-- | addons/stock/models/stock_scrap.py | 21 | ||||
| -rw-r--r-- | addons/stock/tests/__init__.py | 1 | ||||
| -rw-r--r-- | addons/stock/tests/test_stock_scrap.py | 107 | ||||
| -rw-r--r-- | addons/web/static/lib/qweb/qweb-test-widgets.xml | 26 | ||||
| -rw-r--r-- | doc/cla/corporate/eficent.md | 3 | ||||
| -rw-r--r-- | doc/cla/individual/subodhdahal.md | 11 | ||||
| -rw-r--r-- | doc/howtos/themes.rst | 2 | ||||
| -rw-r--r-- | odoo/addons/base/ir/ir_model.py | 2 | ||||
| -rw-r--r-- | odoo/addons/base/ir/ir_qweb/fields.py | 6 | ||||
| -rw-r--r-- | odoo/addons/test_new_api/tests/test_new_fields.py | 13 | ||||
| -rw-r--r-- | odoo/http.py | 10 | ||||
| -rw-r--r-- | odoo/service/security.py | 20 | ||||
| -rw-r--r-- | odoo/tests/common.py | 2 |
18 files changed, 211 insertions, 41 deletions
diff --git a/addons/event/models/event.py b/addons/event/models/event.py index d55a17de099..f11105d5e03 100644 --- a/addons/event/models/event.py +++ b/addons/event/models/event.py @@ -386,9 +386,14 @@ class EventRegistration(models.Model): @api.multi def message_get_suggested_recipients(self): recipients = super(EventRegistration, self).message_get_suggested_recipients() + public_users = self.env['res.users'].sudo() + public_groups = self.env.ref("base.group_public", raise_if_not_found=False) + if public_groups: + public_users = public_groups.sudo().with_context(active_test=False).mapped("users") try: for attendee in self: - if attendee.partner_id: + is_public = attendee.sudo().with_context(active_test=False).partner_id.user_ids in public_users if public_users else False + if attendee.partner_id and not is_public: attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer')) elif attendee.email: attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email')) diff --git a/addons/l10n_nl/data/account_tax_template.xml b/addons/l10n_nl/data/account_tax_template.xml index 07558101a49..b534e9b7369 100644 --- a/addons/l10n_nl/data/account_tax_template.xml +++ b/addons/l10n_nl/data/account_tax_template.xml @@ -549,7 +549,7 @@ <field name="refund_account_id" ref="vat_refund_h_non_eu"/> <field name="account_id" ref="vat_refund_h_non_eu"/> <field name="type_tax_use">none</field> - <field name="tag_ids" eval="[(6,0,[ref('tag_nl_34')])]"/> + <field name="tag_ids" eval="[(6,0,[ref('tag_nl_33')])]"/> </record> <record id="btw_E2" model="account.tax.template"> <field name="sequence">20</field> @@ -680,7 +680,7 @@ <field name="refund_account_id" ref="vat_refund_h_d_non_eu"/> <field name="account_id" ref="vat_refund_h_d_non_eu"/> <field name="type_tax_use">none</field> - <field name="tag_ids" eval="[(6,0,[ref('tag_nl_34')])]"/> + <field name="tag_ids" eval="[(6,0,[ref('tag_nl_33')])]"/> </record> <record id="btw_E2_d" model="account.tax.template"> <field name="sequence">20</field> diff --git a/addons/link_tracker/models/link_tracker.py b/addons/link_tracker/models/link_tracker.py index bdc97778543..c1152d4b43c 100644 --- a/addons/link_tracker/models/link_tracker.py +++ b/addons/link_tracker/models/link_tracker.py @@ -248,19 +248,20 @@ class link_tracker_click(models.Model): vals = { 'link_id': code_rec.link_id.id, - 'create_date': datetime.date.today(), 'ip': ip, 'country_id': country_record.id, - 'mail_stat_id': stat_id } if stat_id: mail_stat = self.env['mail.mail.statistics'].search([('id', '=', stat_id)]) + # It could happen that the related ID is no longer available, but we still want the link to work + if mail_stat.exists(): + vals['mail_stat_id'] = stat_id - if mail_stat.mass_mailing_campaign_id: - vals['mass_mailing_campaign_id'] = mail_stat.mass_mailing_campaign_id.id + if mail_stat.mass_mailing_campaign_id: + vals['mass_mailing_campaign_id'] = mail_stat.mass_mailing_campaign_id.id - if mail_stat.mass_mailing_id: - vals['mass_mailing_id'] = mail_stat.mass_mailing_id.id + if mail_stat.mass_mailing_id: + vals['mass_mailing_id'] = mail_stat.mass_mailing_id.id self.create(vals) diff --git a/addons/payment_ogone/controllers/main.py b/addons/payment_ogone/controllers/main.py index af2e9e05732..a89ff2c63b3 100644 --- a/addons/payment_ogone/controllers/main.py +++ b/addons/payment_ogone/controllers/main.py @@ -48,7 +48,7 @@ class OgoneController(http.Controller): def feedback(self, **kwargs): try: tx = request.env['payment.transaction'].sudo()._ogone_form_get_tx_from_data(kwargs) - tx._ogone_s2s_validate() + tx._ogone_s2s_validate_tree(kwargs) except ValidationError: return 'ko' return 'ok' diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 28f0eb9e1ed..8d13d682f5d 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -663,7 +663,7 @@ class SaleOrderLine(models.Model): if line.state != 'sale' or not line.product_id._need_procurement(): continue qty = 0.0 - for proc in line.procurement_ids: + for proc in line.procurement_ids.filtered(lambda r: r.state != 'cancel'): qty += proc.product_qty if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0: continue diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py index 8453a49b4e3..f921d1eaba6 100644 --- a/addons/stock/models/stock_scrap.py +++ b/addons/stock/models/stock_scrap.py @@ -80,16 +80,17 @@ class StockScrap(models.Model): for scrap in self: moves = scrap._get_origin_moves() or self.env['stock.move'] move = self.env['stock.move'].create(scrap._prepare_move_values()) - quants = self.env['stock.quant'].quants_get_preferred_domain( - move.product_qty, move, - domain=[ - ('qty', '>', 0), - ('lot_id', '=', self.lot_id.id), - ('package_id', '=', self.package_id.id)], - preferred_domain_list=scrap._get_preferred_domain()) - if any([not x[0] for x in quants]): - raise UserError(_('You cannot scrap a move without having available stock for %s. You can correct it with an inventory adjustment.') % move.product_id.name) - self.env['stock.quant'].quants_reserve(quants, move) + if move.product_id.type == 'product': + quants = self.env['stock.quant'].quants_get_preferred_domain( + move.product_qty, move, + domain=[ + ('qty', '>', 0), + ('lot_id', '=', self.lot_id.id), + ('package_id', '=', self.package_id.id)], + preferred_domain_list=scrap._get_preferred_domain()) + if any([not x[0] for x in quants]): + raise UserError(_('You cannot scrap a move without having available stock for %s. You can correct it with an inventory adjustment.') % move.product_id.name) + self.env['stock.quant'].quants_reserve(quants, move) move.action_done() scrap.write({'move_id': move.id, 'state': 'done'}) moves.recalculate_move_state() diff --git a/addons/stock/tests/__init__.py b/addons/stock/tests/__init__.py index bbc9cde189e..06e4428afda 100644 --- a/addons/stock/tests/__init__.py +++ b/addons/stock/tests/__init__.py @@ -4,3 +4,4 @@ from . import test_stock_flow import test_product import test_warehouse import test_shipment +import test_stock_scrap diff --git a/addons/stock/tests/test_stock_scrap.py b/addons/stock/tests/test_stock_scrap.py new file mode 100644 index 00000000000..b5dc521f4b7 --- /dev/null +++ b/addons/stock/tests/test_stock_scrap.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +from openerp.tests.common import TransactionCase +from openerp.exceptions import UserError + + +class TestStockScrap(TransactionCase): + + def setUp(self): + super(TestStockScrap, self).setUp() + self.user_demo = self.env.ref('base.user_demo') + self.scrap_obj = self.env['stock.scrap'] + self.picking_obj = self.env['stock.picking'] + self.stock_loc = self.browse_ref('stock.stock_location_stock') + self.customer_loc = self.browse_ref('stock.stock_location_customers') + + # Stockable product + self.product_stockable = self.env['product.product'].create({ + 'type': 'product', + 'name': 'Stockable Product', + 'uom_id': self.env.ref('product.product_uom_unit').id, + 'uom_po_id': self.env.ref('product.product_uom_unit').id, + }) + + # Consumable product + self.product_consumable = self.env['product.product'].create({ + 'type': 'consu', + 'name': 'Consumable Product', + 'uom_id': self.env.ref('product.product_uom_unit').id, + 'uom_po_id': self.env.ref('product.product_uom_unit').id, + }) + + self._update_product_qty(self.product_stockable) + + def _update_product_qty(self, product): + product_qty = self.env['stock.change.product.qty'].create({ + 'location_id': self.stock_loc.id, + 'product_id': product.id, + 'new_quantity': 100.0, + }) + product_qty.change_product_qty() + return product_qty + + def _prepare_picking(self, product): + picking = self.picking_obj.create({ + 'name': 'picking - test', + 'location_id': self.stock_loc.id, + 'location_dest_id': self.customer_loc.id, + 'picking_type_id': self.ref('stock.picking_type_out'), + 'move_lines': [(0, 0, { + 'name': product.name, + 'product_id': product.id, + 'product_uom_qty': 20.0, + 'product_uom': product.uom_id.id, + })] + }) + return picking + + def test_stock_scrap_01(self): + """ Standalone scrap a stockable product """ + with self.assertRaises(UserError): + self.scrap_obj.create({ + 'name': 'scrap - grand test', + 'product_id': self.product_stockable.id, + 'product_uom_id': self.ref('product.product_uom_unit'), + 'scrap_qty': 120, + }) + self.assertEquals(self.product_stockable.qty_available, 100.0) + + def test_stock_scrap_02(self): + """ Scrap a stockable product from a picking """ + picking = self._prepare_picking(self.product_stockable) + scrap = self.scrap_obj.create({ + 'name': 'scrap - grand test', + 'product_id': self.product_stockable.id, + 'product_uom_id': self.ref('product.product_uom_unit'), + 'picking_id': picking.id, + 'scrap_qty': 80, + }) + self.assertEquals(scrap.state, 'done') + self.assertEquals(self.product_stockable.qty_available, 20.0) + + def test_stock_scrap_03(self): + """ Standalone scrap a consumable product """ + scrap = self.scrap_obj.create({ + 'name': 'scrap - grand test', + 'product_id': self.product_consumable.id, + 'product_uom_id': self.ref('product.product_uom_unit'), + 'scrap_qty': 100, + }) + self.assertEquals(scrap.state, 'done') + self.assertEquals(self.product_consumable.qty_available, -100.0) + with self.assertRaises(UserError): + scrap.sudo(self.user_demo).unlink() + + def test_stock_scrap_04(self): + """ Scrap a consumable product from a picking """ + picking = self._prepare_picking(self.product_consumable) + scrap = self.scrap_obj.create({ + 'name': 'scrap - grand test', + 'product_id': self.product_consumable.id, + 'product_uom_id': self.ref('product.product_uom_unit'), + 'picking_id': picking.id, + 'scrap_qty': 100, + }) + self.assertEquals(scrap.state, 'done') + self.assertEquals(self.product_consumable.qty_available, -100.0) diff --git a/addons/web/static/lib/qweb/qweb-test-widgets.xml b/addons/web/static/lib/qweb/qweb-test-widgets.xml new file mode 100644 index 00000000000..503880bcc34 --- /dev/null +++ b/addons/web/static/lib/qweb/qweb-test-widgets.xml @@ -0,0 +1,26 @@ +<templates> + <t t-name="date-simple"><t t-esc='value' /></t> + <params id="date-simple">{"value": "1988-09-16"}</params> + <result id="date-simple">1988-09-16</result> + + <t t-name="datetime-simple"><t t-esc='value' /></t> + <params id="datetime-simple">{"value": "1988-09-16 14:00:00"}</params> + <result id="datetime-simple">1988-09-16 14:00:00</result> + + <t t-name="datetime-widget-datetime"><t t-esc='value' t-options="{'widget': 'datetime'}" /></t> + <params id="datetime-widget-datetime">{"value": "1988-09-16 14:00:00"}</params> + <result id="datetime-widget-datetime">09/16/1988 16:00:00</result> + + <t t-name="datetime-widget-date"><t t-esc='value' t-options="{'widget': 'date'}" /></t> + <params id="datetime-widget-date">{"value": "1988-09-16 14:00:00"}</params> + <result id="datetime-widget-date">09/16/1988</result> + + <t t-name="datetime-widget-date-tz2"><t t-esc='value' t-options="{'widget': 'date'}" /></t> + <params id="datetime-widget-date-tz2">{"value": "1988-09-16 01:00:00"}</params> + <result id="datetime-widget-date-tz2">09/16/1988</result> + + <t t-name="datetime-widget-date-tz"><t t-esc='value' t-options="{'widget': 'date'}" /></t> + <params id="datetime-widget-date-tz">{"value": "1988-09-16 23:00:00"}</params> + <result id="datetime-widget-date-tz">09/17/1988</result> + +</templates> diff --git a/doc/cla/corporate/eficent.md b/doc/cla/corporate/eficent.md index 3723fbf0740..2b5b8c830c4 100644 --- a/doc/cla/corporate/eficent.md +++ b/doc/cla/corporate/eficent.md @@ -14,6 +14,7 @@ List of contributors: Aarón Henríquez ahenriquez@eficent.com https://github.com/aheficent Lois Rilo lois.rilo@eficent.com https://github.com/lreficent -Gisela Mora gisela.mora@eficent.com https://github.com/gmeficent Miquel Raich miquel.raich@eficent.com https://github.com/mreficent Jordi Ballester jordi.ballester@eficent.com https://github.com/jbeficent +Hector Villarreal hector.villarreal@eficent.com https://github.com/hveficent +Roser Garcia roser.garcia@eficent.com https://github.com/rgarnau (up to 2018-05-01) diff --git a/doc/cla/individual/subodhdahal.md b/doc/cla/individual/subodhdahal.md new file mode 100644 index 00000000000..4e4c389bc99 --- /dev/null +++ b/doc/cla/individual/subodhdahal.md @@ -0,0 +1,11 @@ +Nepal, 2018-02-09 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Subodh Dahal contact.subodhdahal@gmail.com https://github.com/SubodhDahal diff --git a/doc/howtos/themes.rst b/doc/howtos/themes.rst index 531f5e2c8f0..e2dc46f647b 100644 --- a/doc/howtos/themes.rst +++ b/doc/howtos/themes.rst @@ -614,7 +614,7 @@ The previous code will create the snippet’s content, but we still need to plac .. code-block:: xml <template id="place_into_bar" inherit_id="website.snippets" name="Place into bar"> - <xpath expr="//div[@id='snippet_content']/div[@class='o_panel_body']" position="inside"> + <xpath expr="//div[@id='snippet_structure']/div[@class='o_panel_body']" position="inside"> <t t-snippet="theme_tutorial.snippet_testimonial" t-thumbnail="/theme_tutorial/static/src/img/ui/snippet_thumb.jpg"/> </xpath> diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py index c8f4c4122d0..4395de6cc9b 100644 --- a/odoo/addons/base/ir/ir_model.py +++ b/odoo/addons/base/ir/ir_model.py @@ -32,7 +32,7 @@ SAFE_EVAL_BASE = { def make_compute(text, deps): """ Return a compute function from its code body and dependencies. """ func = lambda self: safe_eval(text, SAFE_EVAL_BASE, {'self': self}, mode="exec") - deps = [arg.strip() for arg in (deps or "").split(",")] + deps = [arg.strip() for arg in deps.split(",")] if deps else [] return api.depends(*deps)(func) diff --git a/odoo/addons/base/ir/ir_qweb/fields.py b/odoo/addons/base/ir/ir_qweb/fields.py index 4beec41db7c..28d60940978 100644 --- a/odoo/addons/base/ir/ir_qweb/fields.py +++ b/odoo/addons/base/ir/ir_qweb/fields.py @@ -173,7 +173,11 @@ class DateConverter(models.AbstractModel): locale = babel.Locale.parse(lang.code) if isinstance(value, basestring): - value = fields.Datetime.from_string(value[:10]) + if len(value) > 10: # datetime to be displayed as date + value = fields.Datetime.from_string(value) + value = fields.Datetime.context_timestamp(self, value) + else: + value = fields.Datetime.from_string(value) if options and 'format' in options: pattern = options['format'] diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index b2ff0c1eb58..fd2ce1426c3 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -61,6 +61,19 @@ class TestFields(common.TransactionCase): self.assertTrue(field.store) self.assertTrue(field.readonly) + def test_10_computed_custom(self): + """ check definition of custom computed fields """ + self.env['ir.model.fields'].create({ + 'name': 'x_bool_false_computed', + 'model_id': self.env.ref('test_new_api.model_test_new_api_message').id, + 'field_description': 'A boolean computed to false', + 'compute': "for r in self: r['x_bool_false_computed'] = False", + 'store': False, + 'ttype': 'boolean' + }) + field = self.env['test_new_api.message']._fields['x_bool_false_computed'] + self.assertFalse(field.depends) + def test_10_non_stored(self): """ test non-stored fields """ # a field declared with store=False should not have a column diff --git a/odoo/http.py b/odoo/http.py index 84298c20a63..0075b1bed34 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -1047,7 +1047,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): self.db = db self.uid = uid self.login = login - self.session_token = uid and security.compute_session_token(self) + self.session_token = uid and security.compute_session_token(self, request.env) request.uid = uid request.disable_db = False @@ -1062,15 +1062,17 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): """ if not self.db or not self.uid: raise SessionExpiredException("Session expired") - + # We create our own environment instead of the request's one. + # to avoid creating it without the uid since request.uid isn't set yet + env = odoo.api.Environment(request.cr, self.uid, self.context) # == BACKWARD COMPATIBILITY TO CONVERT OLD SESSION TYPE TO THE NEW ONES ! REMOVE ME AFTER 11.0 == if self.get('password'): security.check(self.db, self.uid, self.password) - self.session_token = security.compute_session_token(self) + self.session_token = security.compute_session_token(self, env) self.pop('password') # ================================================================================================= # here we check if the session is still valid - if not security.check_session(self): + if not security.check_session(self, env): raise SessionExpiredException("Session expired") def logout(self, keep_db=False): diff --git a/odoo/service/security.py b/odoo/service/security.py index 229e4b84077..54d108fb4b1 100644 --- a/odoo/service/security.py +++ b/odoo/service/security.py @@ -12,15 +12,13 @@ def check(db, uid, passwd): res_users = odoo.registry(db)['res.users'] return res_users.check(db, uid, passwd) -def compute_session_token(session): - with odoo.registry(session.db).cursor() as cr: - self = odoo.api.Environment(cr, session.uid, {})['res.users'].browse(session.uid) - return self._compute_session_token(session.sid) +def compute_session_token(session, env): + self = env['res.users'].browse(session.uid) + return self._compute_session_token(session.sid) -def check_session(session): - with odoo.registry(session.db).cursor() as cr: - self = odoo.api.Environment(cr, session.uid, {})['res.users'].browse(session.uid) - if odoo.tools.misc.consteq(self._compute_session_token(session.sid), session.session_token): - return True - self._invalidate_session_cache() - return False +def check_session(session, env): + self = env['res.users'].browse(session.uid) + if odoo.tools.misc.consteq(self._compute_session_token(session.sid), session.session_token): + return True + self._invalidate_session_cache() + return False diff --git a/odoo/tests/common.py b/odoo/tests/common.py index 37000441306..5c88da20e57 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -288,7 +288,7 @@ class HttpCase(TransactionCase): session.db = db session.uid = uid session.login = user - session.session_token = uid and security.compute_session_token(session) + session.session_token = uid and security.compute_session_token(session, env) session.context = env['res.users'].context_get() or {} session.context['uid'] = uid session._fix_lang(session.context) |
