summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-06-09 16:43:27 +0200
committerChristophe Simonis <chs@odoo.com>2017-06-09 16:43:27 +0200
commitcc38b93ab2a6daf9c5badbc387cff20d1a01ddee (patch)
treed5d9942c4101f836a247e609266d3d39f1eeb4ee
parentfb504f3cbd41dc52a81d76aa54d83117730671d0 (diff)
parent49f819a7a797f0c046337118847f143197714e03 (diff)
[MERGE] forward port branch saas-15 up to 49f819a7a79
-rw-r--r--addons/account/models/account.py5
-rw-r--r--addons/account/models/account_bank_statement.py2
-rw-r--r--addons/account/tests/account_test_classes.py27
-rw-r--r--addons/base_geolocalize/models/res_partner.py2
-rw-r--r--addons/base_import_module/models/ir_module.py29
-rw-r--r--addons/calendar/models/calendar.py13
-rw-r--r--addons/crm/models/crm_lead.py1
-rw-r--r--addons/delivery/models/delivery_carrier.py40
-rw-r--r--addons/hr_holidays/models/hr_holidays.py7
-rw-r--r--addons/mrp/models/mrp_production.py7
-rw-r--r--addons/sale/tests/test_sale_order.py3
-rw-r--r--addons/sale_expense/tests/test_sale_expense.py3
-rw-r--r--addons/stock/i18n/stock.pot6
-rw-r--r--addons/stock/models/product.py3
-rw-r--r--addons/stock/models/stock_production_lot.py10
-rw-r--r--addons/stock/models/stock_scrap.py6
-rw-r--r--addons/stock/security/ir.model.access.csv6
-rw-r--r--addons/stock/views/stock_pack_operation_views.xml2
-rw-r--r--addons/stock_account/models/product.py3
-rw-r--r--addons/stock_landed_costs/tests/common.py3
-rw-r--r--addons/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js6
-rw-r--r--addons/web/static/src/js/widgets/date_picker.js1
-rw-r--r--addons/web/static/src/less/webclient.less1
-rw-r--r--addons/web/static/src/xml/base.xml2
-rw-r--r--addons/website/models/website.py5
-rw-r--r--addons/website_crm_partner_assign/models/crm_lead.py2
-rw-r--r--addons/website_event_sale/static/src/js/website.tour.event_sale.js2
-rw-r--r--addons/website_forum/i18n/website_forum.pot36
-rw-r--r--addons/website_forum/static/src/xml/website_forum_share_templates.xml2
-rw-r--r--addons/website_forum/views/website_forum.xml38
-rw-r--r--addons/website_sale/models/sale_order.py10
31 files changed, 210 insertions, 73 deletions
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index fcab29082bd..9354e4731bd 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -468,7 +468,10 @@ class AccountJournal(models.Model):
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
args = args or []
- recs = self.search(['|', ('code', operator, name), ('name', operator, name)] + args, limit=limit)
+ connector = '|'
+ if operator in expression.NEGATIVE_TERM_OPERATORS:
+ connector = '&'
+ recs = self.search([connector, ('code', operator, name), ('name', operator, name)] + args, limit=limit)
return recs.name_get()
@api.multi
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 6aff8481161..0fc05b549bc 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -248,7 +248,7 @@ class AccountBankStatement(models.Model):
for aml in st_line.journal_entry_ids:
moves |= aml.move_id
if moves:
- moves.post()
+ moves.filtered(lambda m: m.state != 'posted').post()
statement.message_post(body=_('Statement %s confirmed, journal items were created.') % (statement.name,))
statements.link_bank_to_partner()
statements.write({'state': 'confirm', 'date_done': time.strftime("%Y-%m-%d %H:%M:%S")})
diff --git a/addons/account/tests/account_test_classes.py b/addons/account/tests/account_test_classes.py
index cf37e2493ef..8477f4109d9 100644
--- a/addons/account/tests/account_test_classes.py
+++ b/addons/account/tests/account_test_classes.py
@@ -28,3 +28,30 @@ class AccountingTestCase(HttpCase):
if theorical_lines:
raise ValidationError('Remaining theorical line (not found). %s)' % ([(aml[0], aml[1], aml[2]) for aml in theorical_lines]))
return True
+
+ def ensure_account_property(self, property_name):
+ '''Ensure the ir.property targetting an account.account passed as parameter exists.
+ In case it's not: create it with a random account. This is useful when testing with
+ partially defined localization (missing stock properties for example)
+
+ :param property_name: The name of the property.
+ '''
+ company_id = self.env.user.company_id
+ field_id = self.env['ir.model.fields'].search(
+ [('model', '=', 'product.template'), ('name', '=', property_name)], limit=1)
+ property_id = self.env['ir.property'].search([
+ ('company_id', '=', company_id.id),
+ ('name', '=', property_name),
+ ('res_id', '=', None),
+ ('fields_id', '=', field_id.id)], limit=1)
+ account_id = self.env['account.account'].search([('company_id', '=', company_id.id)], limit=1)
+ value_reference = 'account.account,%d' % account_id.id
+ if property_id and not property_id.value_reference:
+ property_id.value_reference = value_reference
+ else:
+ self.env['ir.property'].create({
+ 'name': property_name,
+ 'company_id': company_id.id,
+ 'fields_id': field_id.id,
+ 'value_reference': value_reference,
+ })
diff --git a/addons/base_geolocalize/models/res_partner.py b/addons/base_geolocalize/models/res_partner.py
index 778a844ad83..44fa8d9692f 100644
--- a/addons/base_geolocalize/models/res_partner.py
+++ b/addons/base_geolocalize/models/res_partner.py
@@ -8,6 +8,8 @@ from odoo.exceptions import UserError
def geo_find(addr):
+ if not addr:
+ return None
url = 'https://maps.googleapis.com/maps/api/geocode/json?sensor=false&address='
url += urllib2.quote(addr.encode('utf8'))
diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py
index 43eb8f08d6e..889ddf7bb7b 100644
--- a/addons/base_import_module/models/ir_module.py
+++ b/addons/base_import_module/models/ir_module.py
@@ -100,18 +100,23 @@ class IrModule(models.Model):
raise UserError(_("File '%s' exceed maximum allowed file size") % zf.filename)
with tempdir() as module_dir:
- z.extractall(module_dir)
- dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))]
- for mod_name in dirs:
- module_names.append(mod_name)
- try:
- # assert mod_name.startswith('theme_')
- path = opj(module_dir, mod_name)
- self._import_module(mod_name, path, force=force)
- success.append(mod_name)
- except Exception as e:
- _logger.exception('Error while importing module')
- errors[mod_name] = exception_to_unicode(e)
+ import odoo.modules as addons
+ try:
+ addons.module.ad_paths.append(module_dir)
+ z.extractall(module_dir)
+ dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))]
+ for mod_name in dirs:
+ module_names.append(mod_name)
+ try:
+ # assert mod_name.startswith('theme_')
+ path = opj(module_dir, mod_name)
+ self._import_module(mod_name, path, force=force)
+ success.append(mod_name)
+ except Exception as e:
+ _logger.exception('Error while importing module')
+ errors[mod_name] = exception_to_unicode(e)
+ finally:
+ addons.module.ad_paths.remove(module_dir)
r = ["Successfully imported module '%s'" % mod for mod in success]
for mod, error in errors.items():
r.append("Error while importing module '%s': %r" % (mod, error))
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py
index 98f5e5c95d9..0782ae3384a 100644
--- a/addons/calendar/models/calendar.py
+++ b/addons/calendar/models/calendar.py
@@ -567,15 +567,18 @@ class Meeting(models.Model):
if not event_date:
event_date = datetime.now()
- # Convert the event date to saved timezone (or context tz) as it'll
- # define the correct hour/day asked by the user to repeat for recurrence.
- event_date = event_date.astimezone(timezone) # transform "+hh:mm" timezone
- rset1 = rrule.rrulestr(str(self.rrule), dtstart=event_date, forceset=True)
+ if self.allday and self.rrule and 'UNTIL' in self.rrule and 'Z' not in self.rrule:
+ rset1 = rrule.rrulestr(str(self.rrule), dtstart=event_date.replace(tzinfo=None), forceset=True, ignoretz=True)
+ else:
+ # Convert the event date to saved timezone (or context tz) as it'll
+ # define the correct hour/day asked by the user to repeat for recurrence.
+ event_date = event_date.astimezone(timezone) # transform "+hh:mm" timezone
+ rset1 = rrule.rrulestr(str(self.rrule), dtstart=event_date, forceset=True, tzinfos={})
recurring_meetings = self.search([('recurrent_id', '=', self.id), '|', ('active', '=', False), ('active', '=', True)])
for meeting in recurring_meetings:
rset1._exdate.append(todate(meeting.recurrent_id_date))
- return [d.astimezone(pytz.UTC) for d in rset1]
+ return [d.astimezone(pytz.UTC) if d.tzinfo else d for d in rset1]
@api.multi
def _get_recurrency_end_date(self):
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index fb4472f43e8..5ea52c874e2 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -247,6 +247,7 @@ class Lead(models.Model):
team_id = self.env['crm.team']._get_default_team_id(user_id=user_id)
return {'team_id': team_id}
+ @api.constrains('user_id')
@api.onchange('user_id')
def _onchange_user_id(self):
""" When changing the user, also set a team_id or restrict team id to the ones user_id is member of. """
diff --git a/addons/delivery/models/delivery_carrier.py b/addons/delivery/models/delivery_carrier.py
index 543c0feb0b7..dc91bf4e5e6 100644
--- a/addons/delivery/models/delivery_carrier.py
+++ b/addons/delivery/models/delivery_carrier.py
@@ -226,26 +226,26 @@ class DeliveryCarrier(models.Model):
if record.delivery_type == 'fixed':
PriceRule.search([('carrier_id', '=', record.id)]).unlink()
- line_data = {
- 'carrier_id': record.id,
- 'variable': 'price',
- 'operator': '>=',
- }
- # Create the delivery price rules
- if record.free_if_more_than:
- line_data.update({
- 'max_value': record.amount,
- 'standard_price': 0.0,
- 'list_base_price': 0.0,
- })
- PriceRule.create(line_data)
- if record.fixed_price is not False:
- line_data.update({
- 'max_value': 0.0,
- 'standard_price': record.fixed_price,
- 'list_base_price': record.fixed_price,
- })
- PriceRule.create(line_data)
+ line_data = {
+ 'carrier_id': record.id,
+ 'variable': 'price',
+ 'operator': '>=',
+ }
+ # Create the delivery price rules
+ if record.free_if_more_than:
+ line_data.update({
+ 'max_value': record.amount,
+ 'standard_price': 0.0,
+ 'list_base_price': 0.0,
+ })
+ PriceRule.create(line_data)
+ if record.fixed_price is not False:
+ line_data.update({
+ 'max_value': 0.0,
+ 'standard_price': record.fixed_price,
+ 'list_base_price': record.fixed_price,
+ })
+ PriceRule.create(line_data)
return True
@api.model
diff --git a/addons/hr_holidays/models/hr_holidays.py b/addons/hr_holidays/models/hr_holidays.py
index eff33ce5af3..2ad8de7130e 100644
--- a/addons/hr_holidays/models/hr_holidays.py
+++ b/addons/hr_holidays/models/hr_holidays.py
@@ -329,9 +329,12 @@ class Holidays(models.Model):
res = []
for leave in self:
if leave.type == 'remove':
- res.append((leave.id, _("%s on %s : %.2f day(s)") % (leave.employee_id.name or leave.category_id.name, leave.holiday_status_id.name, leave.number_of_days_temp)))
+ if self.env.context.get('short_name'):
+ res.append((leave.id, _("%s : %.2f day(s)") % (leave.name or leave.holiday_status_id.name, leave.number_of_days_temp)))
+ else:
+ res.append((leave.id, _("%s on %s : %.2f day(s)") % (leave.employee_id.name or leave.category_id.name, leave.holiday_status_id.name, leave.number_of_days_temp)))
else:
- res.append((leave.id, _("Allocation of %s : %.2f day(s) To %s") % (leave.holiday_status_id.name, leave.number_of_days_temp,leave.employee_id.name)))
+ res.append((leave.id, _("Allocation of %s : %.2f day(s) To %s") % (leave.holiday_status_id.name, leave.number_of_days_temp, leave.employee_id.name)))
return res
def _check_state_access_right(self, vals):
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index a797892a4c2..4f728d48698 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -208,8 +208,11 @@ class MrpProduction(models.Model):
@api.depends('move_raw_ids.quantity_done', 'move_finished_ids.quantity_done')
def _compute_post_visible(self):
for order in self:
- order.post_visible = any(order.move_raw_ids.filtered(lambda x: (x.quantity_done) > 0 and (x.state not in ['done', 'cancel']))) or \
- any(order.move_finished_ids.filtered(lambda x: (x.quantity_done) > 0 and (x.state not in ['done', 'cancel'])))
+ if order.product_tmpl_id._is_cost_method_standard():
+ order.post_visible = any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_raw_ids) or \
+ any((x.quantity_done > 0 and x.state not in ['done' 'cancel']) for x in order.move_finished_ids)
+ else:
+ order.post_visible = any((x.quantity_done > 0 and x.state not in ['done' 'cancel']) for x in order.move_finished_ids)
@api.multi
@api.depends('workorder_ids.state', 'move_finished_ids')
diff --git a/addons/sale/tests/test_sale_order.py b/addons/sale/tests/test_sale_order.py
index 4022c85c179..248cb435ff2 100644
--- a/addons/sale/tests/test_sale_order.py
+++ b/addons/sale/tests/test_sale_order.py
@@ -104,6 +104,9 @@ class TestSaleOrder(TestSale):
def test_cost_invoicing(self):
""" Test confirming a vendor invoice to reinvoice cost on the so """
+ # force the pricelist to have the same currency as the company
+ self.env.ref('product.list0').currency_id = self.env.ref('base.main_company').currency_id
+
serv_cost = self.env.ref('product.service_cost_01')
prod_gap = self.env.ref('product.product_product_1')
so = self.env['sale.order'].create({
diff --git a/addons/sale_expense/tests/test_sale_expense.py b/addons/sale_expense/tests/test_sale_expense.py
index 2dd9dba7726..9b3fa6cd79a 100644
--- a/addons/sale_expense/tests/test_sale_expense.py
+++ b/addons/sale_expense/tests/test_sale_expense.py
@@ -7,6 +7,9 @@ from odoo.addons.sale.tests.test_sale_common import TestSale
class TestSaleExpense(TestSale):
def test_sale_expense(self):
""" Test the behaviour of sales orders when managing expenses """
+ # force the pricelist to have the same currency as the company
+ self.env.ref('product.list0').currency_id = self.env.ref('base.main_company').currency_id
+
# create a so with a product invoiced on delivery
prod = self.env.ref('product.product_product_1')
so = self.env['sale.order'].create({
diff --git a/addons/stock/i18n/stock.pot b/addons/stock/i18n/stock.pot
index 1296f083384..8e86883b4aa 100644
--- a/addons/stock/i18n/stock.pot
+++ b/addons/stock/i18n/stock.pot
@@ -6384,6 +6384,12 @@ msgid "You cannot set a negative product quantity in an inventory line:\n"
msgstr ""
#. module: stock
+#: code:addons/stock/models/stock_production_lot.py:37
+#, python-format
+msgid "You are not allowed to create a lot for this picking type"
+msgstr ""
+
+#. module: stock
#: code:addons/stock/models/stock_move.py:927
#, python-format
msgid "You cannot split a draft move. It needs to be confirmed first."
diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py
index 54b4a877fd7..6e26cf6dc77 100644
--- a/addons/stock/models/product.py
+++ b/addons/stock/models/product.py
@@ -419,6 +419,9 @@ class ProductTemplate(models.Model):
relation="stock.location.route", string="Category Routes",
related='categ_id.total_route_ids')
+ def _is_cost_method_standard(self):
+ return True
+
def _compute_quantities(self):
res = self._compute_quantities_dict()
for template in self:
diff --git a/addons/stock/models/stock_production_lot.py b/addons/stock/models/stock_production_lot.py
index 60f370fff51..a568cd80fe3 100644
--- a/addons/stock/models/stock_production_lot.py
+++ b/addons/stock/models/stock_production_lot.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
+from odoo.exceptions import UserError
class ProductionLot(models.Model):
@@ -27,6 +28,15 @@ class ProductionLot(models.Model):
('name_ref_uniq', 'unique (name, product_id)', 'The combination of serial number and product must be unique !'),
]
+ @api.model
+ def create(self, vals):
+ pack_id = self.env.context.get('active_pack_operation', False)
+ if pack_id:
+ pack = self.env['stock.pack.operation'].browse(pack_id)
+ if pack.picking_id and not pack.picking_id.picking_type_id.use_create_lots:
+ raise UserError(_("You are not allowed to create a lot for this picking type"))
+ return super(ProductionLot, self).create(vals)
+
@api.one
@api.depends('quant_ids.qty')
def _product_qty(self):
diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py
index 20de2d25775..8453a49b4e3 100644
--- a/addons/stock/models/stock_scrap.py
+++ b/addons/stock/models/stock_scrap.py
@@ -66,6 +66,12 @@ class StockScrap(models.Model):
scrap.do_scrap()
return scrap
+ @api.multi
+ def unlink(self):
+ if 'done' in self.mapped('state'):
+ raise UserError(_('You cannot delete a scrap which is done.'))
+ return super(StockScrap, self).unlink()
+
def _get_origin_moves(self):
return self.picking_id and self.picking_id.move_lines.filtered(lambda x: x.product_id == self.product_id)
diff --git a/addons/stock/security/ir.model.access.csv b/addons/stock/security/ir.model.access.csv
index dc8f1756134..6b13597199d 100644
--- a/addons/stock/security/ir.model.access.csv
+++ b/addons/stock/security/ir.model.access.csv
@@ -78,4 +78,8 @@ access_barcode_rule_stock_manager,barcode.rule.stock.manager,barcodes.model_barc
access_stock_forecast_user,report.stock.forecast.user,model_report_stock_forecast,stock.group_stock_user,1,0,0,0
access_stock_forecast_manager,report.stock.forecast.manager,model_report_stock_forecast,stock.group_stock_manager,1,1,1,1
access_stock_scrap_user,stock.scrap.user,model_stock_scrap,stock.group_stock_user,1,1,1,0
-access_procurement_order,procurement.order,stock.model_procurement_order,base.group_portal,1,0,0,0 \ No newline at end of file
+access_procurement_order,procurement.order,stock.model_procurement_order,base.group_portal,1,0,0,0
+access_product_attribute_manager,product.attribute manager,product.model_product_attribute,stock.group_stock_manager,1,1,1,1
+access_product_attribute_value_manager,product.attribute manager value,product.model_product_attribute_value,stock.group_stock_manager,1,1,1,1
+access_product_attribute_price_manager,product.attribute manager price,product.model_product_attribute_price,stock.group_stock_manager,1,1,1,1
+access_product_attribute_line_manager,product.attribute manager line,product.model_product_attribute_line,stock.group_stock_manager,1,1,1,1 \ No newline at end of file
diff --git a/addons/stock/views/stock_pack_operation_views.xml b/addons/stock/views/stock_pack_operation_views.xml
index 5b6919dab83..cd8469d4672 100644
--- a/addons/stock/views/stock_pack_operation_views.xml
+++ b/addons/stock/views/stock_pack_operation_views.xml
@@ -85,7 +85,7 @@
<field name="lot_name" invisible="not context.get('only_create', False)"/>
<field name="lot_id" invisible="context.get('only_create', False)"
domain="[('product_id','=', parent.product_id)]"
- context="{'default_product_id': parent.product_id}"/>
+ context="{'default_product_id': parent.product_id, 'active_pack_operation': parent.id}"/>
<field name="qty_todo" invisible="not context.get('show_reserved') or context.get('serial') or context.get('state_done')" readonly="1"/>
<field name="qty" invisible="context.get('serial')"/>
<button name="do_minus" type="object" icon="fa-minus-square" attrs="{'invisible': [('qty', '&lt;=', 0.99)]}" invisible="not context.get('show_reserved') or context.get('state_done')"/>
diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py
index 1e4ac7ffbe1..1b15d2aa066 100644
--- a/addons/stock_account/models/product.py
+++ b/addons/stock_account/models/product.py
@@ -51,6 +51,9 @@ class ProductTemplate(models.Model):
def _compute_cost_method(self):
self.cost_method = self.property_cost_method or self.categ_id.property_cost_method
+ def _is_cost_method_standard(self):
+ return self.property_cost_method == 'standard'
+
@api.one
def _set_cost_method(self):
return self.write({'property_cost_method': self.cost_method})
diff --git a/addons/stock_landed_costs/tests/common.py b/addons/stock_landed_costs/tests/common.py
index cdd99228dab..205d2171853 100644
--- a/addons/stock_landed_costs/tests/common.py
+++ b/addons/stock_landed_costs/tests/common.py
@@ -59,6 +59,9 @@ class TestStockLandedCostsCommon(AccountingTestCase):
self.brokerage_quantity = self._create_services('Brokerage Cost')
self.transportation_weight = self._create_services('Transportation Cost')
self.packaging_volume = self._create_services('Packaging Cost')
+ # Ensure the account properties exists.
+ self.ensure_account_property('property_stock_account_input')
+ self.ensure_account_property('property_stock_account_output')
def _create_services(self, name):
return self.Product.create({
diff --git a/addons/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js b/addons/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js
index 807bdff70dd..8f0724bbb00 100644
--- a/addons/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js
+++ b/addons/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js
@@ -421,6 +421,12 @@
element.children().first().after(widget);
}
+ // !! ODOO FIX START !!
+ var parentOffset = parent.offset();
+ position.top = offset.top - parentOffset.top;
+ position.left = offset.left - parentOffset.left;
+ // !! ODOO FIX END !!
+
// Top and bottom logic
if (vertical === 'auto') {
if (offset.top + widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() &&
diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js
index 353a5dc78f2..76281d9366a 100644
--- a/addons/web/static/src/js/widgets/date_picker.js
+++ b/addons/web/static/src/js/widgets/date_picker.js
@@ -42,6 +42,7 @@ var DateWidget = Widget.extend({
locale : moment.locale(),
allowInputToggle: true,
keyBinds: null,
+ widgetParent: 'body',
});
},
/**
diff --git a/addons/web/static/src/less/webclient.less b/addons/web/static/src/less/webclient.less
index 2f9878fbdc3..5e84d74b599 100644
--- a/addons/web/static/src/less/webclient.less
+++ b/addons/web/static/src/less/webclient.less
@@ -12,6 +12,7 @@ a {
// General
// ------------------------------------------------------------------
.o_web_client {
+ position: relative; // normally useless but required by bootstrap-datepicker
background-color: @odoo-webclient-background-color;
}
.o_text_overflow {
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index c4a3cc3939f..dbd52954367 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -24,7 +24,7 @@
<div t-name="CrashManager.warning" class="o_dialog_warning">
<t t-js="d">
- var message = d.message ? d.message : d.error.data.message;
+ var message = (d.message !== undefined) ? d.message : d.error.data.message;
d.html_error = context.engine.tools.html_escape(message).replace(/\n/g, '<br/>');
</t>
<t t-raw="html_error"/>
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index bd7621c767d..f6fe1bc9c77 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -397,9 +397,10 @@ class Website(models.Model):
@api.model
def get_current_website(self):
- domain_name = request.httprequest.environ.get('HTTP_HOST', '').split(':')[0]
+ domain_name = request and request.httprequest.environ.get('HTTP_HOST', '').split(':')[0] or None
website_id = self._get_current_website_id(domain_name)
- request.context = dict(request.context, website_id=website_id)
+ if request:
+ request.context = dict(request.context, website_id=website_id)
return self.browse(website_id)
@tools.cache('domain_name')
diff --git a/addons/website_crm_partner_assign/models/crm_lead.py b/addons/website_crm_partner_assign/models/crm_lead.py
index 99a39ac9c91..424919b05ab 100644
--- a/addons/website_crm_partner_assign/models/crm_lead.py
+++ b/addons/website_crm_partner_assign/models/crm_lead.py
@@ -47,8 +47,6 @@ class CrmLead(models.Model):
for salesman_id, leads_ids in salesmans_leads.items():
leads = self.browse(leads_ids)
leads.write({'user_id': salesman_id})
- for lead in leads:
- lead._onchange_user_id()
@api.multi
def action_assign_partner(self):
diff --git a/addons/website_event_sale/static/src/js/website.tour.event_sale.js b/addons/website_event_sale/static/src/js/website.tour.event_sale.js
index f6638573256..b099ebd6ebe 100644
--- a/addons/website_event_sale/static/src/js/website.tour.event_sale.js
+++ b/addons/website_event_sale/static/src/js/website.tour.event_sale.js
@@ -75,7 +75,7 @@ tour.register('event_buy_tickets', {
trigger: 'a[href="/shop/confirm_order"]:contains("Confirm")',
},
{
- content: "Check that the subtotal is 5,500.00",
+ content: "Check that the subtotal is 5,500.00 USD", // this test will fail if the currency of the main company is not USD
trigger: '#order_total_untaxed .oe_currency_value:contains("5,500.00")',
run: function () {}, // it's a check
},
diff --git a/addons/website_forum/i18n/website_forum.pot b/addons/website_forum/i18n/website_forum.pot
index 5fddf1e74a5..2f70c77241b 100644
--- a/addons/website_forum/i18n/website_forum.pot
+++ b/addons/website_forum/i18n/website_forum.pot
@@ -604,6 +604,7 @@ msgid "Bad Request"
msgstr ""
#. module: website_forum
+#: model:ir.model.fields,field_description:website_forum.field_res_users_badge_ids
#: model:ir.ui.view,arch_db:website_forum.badge
#: model:ir.ui.view,arch_db:website_forum.header
#: model:ir.ui.view,arch_db:website_forum.user_detail_full
@@ -782,6 +783,7 @@ msgstr ""
#. openerp-web
#: code:addons/website_forum/static/src/xml/website_forum_share_templates.xml:46
#: selection:forum.post,state:0
+#: model:ir.ui.view,arch_db:website_forum.post_description_full
#, python-format
msgid "Close"
msgstr ""
@@ -884,6 +886,16 @@ msgid "Convert all answers to comments and vice versa"
msgstr ""
#. module: website_forum
+#: model:ir.ui.view,arch_db:website_forum.post_answers
+msgid "Convert as a comment"
+msgstr ""
+
+#. module: website_forum
+#: model:ir.ui.view,arch_db:website_forum.post_comment
+msgid "Convert as an answer"
+msgstr ""
+
+#. module: website_forum
#: model:ir.model.fields,field_description:website_forum.field_forum_post_karma_accept
msgid "Convert comment to answer"
msgstr ""
@@ -969,6 +981,12 @@ msgid "Default Post"
msgstr ""
#. module: website_forum
+#: model:ir.ui.view,arch_db:website_forum.post_answers
+#: model:ir.ui.view,arch_db:website_forum.post_description_full
+msgid "Delete"
+msgstr ""
+
+#. module: website_forum
#: model:ir.model.fields,field_description:website_forum.field_forum_forum_karma_unlink_all
msgid "Delete all posts"
msgstr ""
@@ -1038,6 +1056,8 @@ msgstr ""
#. module: website_forum
#: model:ir.ui.view,arch_db:website_forum.edit_post
+#: model:ir.ui.view,arch_db:website_forum.post_answers
+#: model:ir.ui.view,arch_db:website_forum.post_description_full
msgid "Edit"
msgstr ""
@@ -1185,6 +1205,11 @@ msgid "First upvote"
msgstr ""
#. module: website_forum
+#: model:ir.ui.view,arch_db:website_forum.post_description_full
+msgid "Flag"
+msgstr ""
+
+#. module: website_forum
#: model:ir.model.fields,field_description:website_forum.field_forum_forum_karma_flag
#: model:ir.model.fields,field_description:website_forum.field_forum_post_karma_flag
msgid "Flag a post as offensive"
@@ -1193,6 +1218,7 @@ msgstr ""
#. module: website_forum
#: selection:forum.post,state:0
#: model:ir.ui.view,arch_db:website_forum.moderation_queue
+#: model:ir.ui.view,arch_db:website_forum.post_description_full
msgid "Flagged"
msgstr ""
@@ -2016,6 +2042,11 @@ msgid "Re: %s"
msgstr ""
#. module: website_forum
+#: model:ir.ui.view,arch_db:website_forum.post_description_full
+msgid "Reactivate"
+msgstr ""
+
+#. module: website_forum
#: model:ir.ui.view,arch_db:website_forum.header
msgid "Read Guidelines"
msgstr ""
@@ -2068,6 +2099,11 @@ msgid "Relevance Computation"
msgstr ""
#. module: website_forum
+#: model:ir.ui.view,arch_db:website_forum.post_description_full
+msgid "Reopen"
+msgstr ""
+
+#. module: website_forum
#: model:ir.ui.view,arch_db:website_forum.post_answers
#: model:ir.ui.view,arch_db:website_forum.post_description_full
msgid "Reply"
diff --git a/addons/website_forum/static/src/xml/website_forum_share_templates.xml b/addons/website_forum/static/src/xml/website_forum_share_templates.xml
index ab44aa48fcb..670e143ced8 100644
--- a/addons/website_forum/static/src/xml/website_forum_share_templates.xml
+++ b/addons/website_forum/static/src/xml/website_forum_share_templates.xml
@@ -33,7 +33,7 @@
<t t-name="website_forum.social_message_answer">
<p>By sharing you answer, you will get additional <b>karma points</b> if your
answer is selected as the right one. See what you can do with karma
- <a href="https://www.odoo.com/forum/how-to/miscellaneous-14/how-does-karma-work-odoo-v8-forum-software-57845" target="_blank">here</a>.</p>
+ <a href="/forum/help-1/faq" target="_blank">here</a>.</p>
</t>
<t t-name="website_forum.social_message_default">
<p>Share this content to increase your chances to be featured on the front page and attract more visitors.</p>
diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml
index 74df06b10cb..3608b9b47c7 100644
--- a/addons/website_forum/views/website_forum.xml
+++ b/addons/website_forum/views/website_forum.xml
@@ -793,9 +793,10 @@
</div>
</div>
<div class="text-muted mb16">
- <i t-if="question.post_type == 'question'" t-attf-class="fa fa-question-circle" t-att-title="question.post_type.capitalize()" />
- <i t-if="question.post_type == 'discussion'" t-attf-class="fa fa-comments-o" t-att-title="question.post_type.capitalize()" />
- <i t-if="question.post_type == 'link'" t-attf-class="fa fa-link" t-att-title="question.post_type.capitalize()" />
+ <t t-set="type_labels" t-value="dict(question._fields['post_type']._description_selection(question.env))"/>
+ <i t-if="question.post_type == 'question'" t-attf-class="fa fa-question-circle" t-att-title="type_labels[question.post_type]" />
+ <i t-if="question.post_type == 'discussion'" t-attf-class="fa fa-comments-o" t-att-title="type_labels[question.post_type]" />
+ <i t-if="question.post_type == 'link'" t-attf-class="fa fa-link" t-att-title="type_labels[question.post_type]" />
<span>By </span>
<a t-att-href="(question.create_uid.id == request.session.uid or question.create_uid.sudo().website_published) and '/forum/%s/user/%s' % (slug(forum), question.create_uid.id) or None"
t-field="question.create_uid"
@@ -819,7 +820,7 @@
<div class="mt16 mb24 text-center">
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) + '/question/' + slug(question) + '/reopen'"/>
- <t t-set="label" t-value="'Reopen'"/>
+ <t t-set="label">Reopen</t>
<t t-set="classes" t-value="'fa-arrow-right'"/>
<t t-set="karma" t-value="not question.can_close and question.karma_close or 0"/>
</t>
@@ -832,7 +833,7 @@
<li t-if="question.state == 'close'">
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/reopen'"/>
- <t t-set="label" t-value="'Reopen'"/>
+ <t t-set="label">Reopen</t>
<t t-set="classes" t-value="'fa-undo'"/>
<t t-set="karma" t-value="not question.can_close and question.karma_close or 0"/>
</t>
@@ -840,7 +841,7 @@
<li>
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) +'/post/' + slug(question) + '/edit'"/>
- <t t-set="label" t-value="'Edit'"/>
+ <t t-set="label">Edit</t>
<t t-set="classes" t-value="'fa-pencil-square-o'"/>
<t t-set="karma" t-value="not question.can_edit and question.karma_edit or 0"/>
</t>
@@ -848,7 +849,7 @@
<li t-if="question.state != 'close'">
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/ask_for_close'"/>
- <t t-set="label" t-value="'Close'"/>
+ <t t-set="label">Close</t>
<t t-set="classes" t-value="'fa-times'"/>
<t t-set="karma" t-value="not question.can_close and question.karma_close or 0"/>
</t>
@@ -856,7 +857,7 @@
<li t-if="question.active">
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/delete'"/>
- <t t-set="label" t-value="'Delete'"/>
+ <t t-set="label">Delete</t>
<t t-set="classes" t-value="'fa-trash-o'"/>
<t t-set="karma" t-value="not question.can_unlink and question.karma_unlink or 0"/>
</t>
@@ -864,7 +865,7 @@
<li t-if="not question.active and question.state != 'offensive'">
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/undelete'"/>
- <t t-set="label" t-value="'Reactivate'"/>
+ <t t-set="label">Reactivate</t>
<t t-set="classes" t-value="'fa-trash-o'"/>
<t t-set="karma" t-value="not question.can_unlink and question.karma_unlink or 0"/>
</t>
@@ -872,7 +873,7 @@
<li t-if="not question.active and question.state == 'offensive'">
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) +'/post/' + slug(question) + '/validate'"/>
- <t t-set="label" t-value="'Reactivate'"/>
+ <t t-set="label">Reactivate</t>
<t t-set="classes" t-value="'fa-trash-o'"/>
<t t-set="karma" t-value="not question.can_moderate and question.forum_id.karma_moderate or 0"/>
</t>
@@ -883,7 +884,12 @@
t-attf-data-karma="#{not question.can_flag and question.forum_id.karma_flag or 0}"
t-attf-data-href="/forum/#{slug(forum)}/post/#{slug(question)}/flag"
>
- <t class="flag_flagged_label" t-esc="question.state == 'flagged' and 'Flagged' or 'Flag'"/>
+ <t t-if="question.state == 'flagged'">
+ Flagged
+ </t>
+ <t t-if="question.state != 'flagged'">
+ Flag
+ </t>
</a>
</li>
</ul>
@@ -982,7 +988,7 @@
<li>
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(answer) + '/edit'"/>
- <t t-set="label" t-value="'Edit'"/>
+ <t t-set="label">Edit</t>
<t t-set="classes" t-value="'fa fa-pencil-square-o'"/>
<t t-set="karma" t-value="not answer.can_edit and answer.karma_edit or 0"/>
</t>
@@ -990,7 +996,7 @@
<li>
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(answer) + '/delete'"/>
- <t t-set="label" t-value="'Delete'"/>
+ <t t-set="label">Delete</t>
<t t-set="classes" t-value="'fa-trash-o'"/>
<t t-set="karma" t-value="not answer.can_unlink and answer.karma_unlink or 0"/>
</t>
@@ -1007,7 +1013,7 @@
<li t-if="question.post_type == 'question'">
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(answer) + '/convert_to_comment'"/>
- <t t-set="label" t-value="'Convert as a comment'"/>
+ <t t-set="label">Convert as a comment</t>
<t t-set="classes" t-value="'fa-magic'"/>
<t t-set="karma" t-value="not answer.can_comment_convert and answer.karma_comment_convert or 0"/>
</t>
@@ -1081,7 +1087,7 @@
<t t-set="required_karma" t-value="message.author_id.id == user.partner_id.id and object.forum_id.karma_comment_unlink_own or object.forum_id.karma_comment_unlink_all"/>
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(object) + '/comment/' + slug(message) + '/delete'"/>
- <t t-set="label" t-value="' '"/>
+ <t t-set="label"> </t>
<t t-set="karma" t-value="user.karma&lt;required_karma and required_karma or 0"/>
<t t-set="classes" t-value="'close comment_delete fa-times'"/>
</t>
@@ -1094,7 +1100,7 @@
<t t-if="allow_post_comment">
<t t-call="website_forum.link_button" >
<t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(object) + '/comment/' + slug(message) + '/convert_to_answer'"/>
- <t t-set="label" t-value="'Convert as an answer'"/>
+ <t t-set="label">Convert as an answer</t>
<t t-set="karma" t-value="user.karma&lt;required_karma and required_karma or 0"/>
<t t-set="classes" t-value="'fa-magic pull-right'"/>
</t>
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index 7ccb2f531b1..c8a39dba78a 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -244,19 +244,19 @@ class Website(models.Model):
:param bool show_visible: if True, we don't display pricelist where selectable is False (Eg: Code promo)
:returns: pricelist recordset
"""
- website = request.website
- if not request.website:
+ website = request and request.website or None
+ if not website:
if self.env.context.get('website_id'):
website = self.browse(self.env.context['website_id'])
else:
website = self.search([], limit=1)
- isocountry = request.session.geoip and request.session.geoip.get('country_code') or False
+ isocountry = request and request.session.geoip and request.session.geoip.get('country_code') or False
partner = self.env.user.partner_id
order_pl = partner.last_website_so_id and partner.last_website_so_id.state == 'draft' and partner.last_website_so_id.pricelist_id
partner_pl = partner.property_product_pricelist
pricelists = website._get_pl_partner_order(isocountry, show_visible,
website.user_id.sudo().partner_id.property_product_pricelist.id,
- request.session.get('website_sale_current_pl'),
+ request and request.session.get('website_sale_current_pl') or None,
website.pricelist_ids,
partner_pl=partner_pl and partner_pl.id or None,
order_pl=order_pl and order_pl.id or None)
@@ -280,7 +280,7 @@ class Website(models.Model):
available_pricelists = self.get_pricelist_available()
pl = None
partner = self.env.user.partner_id
- if request.session.get('website_sale_current_pl'):
+ if request and request.session.get('website_sale_current_pl'):
# `website_sale_current_pl` is set only if the user specifically chose it:
# - Either, he chose it from the pricelist selection
# - Either, he entered a coupon code