[MERGE] forward port branch saas-12.2 up to 8747d72f1e

This commit is contained in:
Christophe Simonis
2019-06-21 15:16:54 +02:00
83 changed files with 1237 additions and 834 deletions
+1
View File
@@ -1,6 +1,7 @@
include requirements.txt
include LICENSE
include README.md
include odoo/addons/mail/static/scripts/odoo-mailgate.py
recursive-include odoo *.css
recursive-include odoo *.csv
recursive-include odoo *.doc
+4 -1
View File
@@ -98,7 +98,10 @@ class AccountInvoice(models.Model):
currency_clause = [('currency_id', '=', currency_id)]
if currency_id == company_currency_id:
currency_clause = ['|', ('currency_id', '=', False)] + currency_clause
return self.env['account.journal'].search(domain + currency_clause, limit=1)
return (
self.env['account.journal'].search(domain + currency_clause, limit=1)
or self.env['account.journal'].search(domain, limit=1)
)
@api.model
def _default_currency(self):
+1 -1
View File
@@ -482,8 +482,8 @@ class AccountMove(models.Model):
def action_duplicate(self):
self.ensure_one()
action = self.env.ref('account.action_move_journal_line').read()[0]
action['target'] = 'inline'
action['context'] = dict(self.env.context)
action['context']['form_view_initial_mode'] = 'edit'
action['context']['view_no_maturity'] = False
action['views'] = [(self.env.ref('account.view_move_form').id, 'form')]
action['res_id'] = self.copy().id
@@ -467,6 +467,8 @@ class AccountReconcileModel(models.Model):
(
line_partner.partner_id = 0
AND
TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')) != ''
AND
(
regexp_split_to_array(TRIM(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g')),'\s+')
&& regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
@@ -12,6 +12,7 @@
<field name="composition_mode" invisible="1"/>
<field name="composer_id" invisible="1"/>
<field name="invoice_ids" invisible="1"/>
<field name="email_from" invisible="1" />
<div name="option_print">
<field name="is_print" />
<b><label for="is_print"/></b>
@@ -305,9 +305,12 @@ class AccountVoucher(models.Model):
}
# Create one line per tax and fix debit-credit for the move line if there are tax included
if (line.tax_ids):
tax_group = line.tax_ids.compute_all(line.price_unit, line.currency_id, line.quantity, line.product_id, self.partner_id)
tax_group = line.tax_ids.compute_all(self._convert(line.price_unit), self.company_id.currency_id, line.quantity, line.product_id, self.partner_id)
if move_line['debit']: move_line['debit'] = tax_group['total_excluded']
if move_line['credit']: move_line['credit'] = tax_group['total_excluded']
Currency = self.env['res.currency']
company_cur = Currency.browse(company_currency)
current_cur = Currency.browse(current_currency)
for tax_vals in tax_group['taxes']:
if tax_vals['amount']:
tax = self.env['account.tax'].browse([tax_vals['id']])
@@ -328,8 +331,8 @@ class AccountVoucher(models.Model):
ctx = {}
if self.account_date:
ctx['date'] = self.account_date
temp['currency_id'] = current_currency.id
temp['amount_currency'] = company_currency._convert(tax_vals['amount'], current_currency, line.company_id, self.account_date or fields.Date.today(), round=True)
temp['currency_id'] = current_currency
temp['amount_currency'] = company_cur._convert(tax_vals['amount'], current_cur, line.company_id, self.account_date or fields.Date.today(), round=True)
self.env['account.move.line'].create(temp)
self.env['account.move.line'].create(move_line)
+3 -1
View File
@@ -83,7 +83,9 @@ class SaleOrderLine(models.Model):
company = self.event_id.company_id or self.env.user.company_id
currency = company.currency_id or self.env.user.company_id.currency_id
self.price_unit = currency._convert(
self.event_ticket_id.price, self.order_id.currency_id, self.order_id.company_id, self.order_id.date_order or fields.Date.today())
self.event_ticket_id.price, self.order_id.currency_id,
self.order_id.company_id or self.env.user.company_id,
self.order_id.date_order or fields.Date.today())
# we call this to force update the default name
self.product_id_change()
@@ -15,7 +15,7 @@
<field name="description_motivational" type="html">
<div class="media align-items-center">
<div class="media-body">Reach the next rank to show the rest of the world you exist!</div>
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="gamification/static/img/rank_student_badge.svg"/>
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_student_badge.svg"/>
</div>
</field>
<field name="karma_min">1</field>
@@ -28,7 +28,7 @@
<field name="description_motivational" type="html">
<div class="media align-items-center">
<div class="media-body">Reach the next rank to improve your status.</div>
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="gamification/static/img/rank_bachelor_badge.svg"/>
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_bachelor_badge.svg"/>
</div>
</field>
<field name="karma_min">100</field>
@@ -41,7 +41,7 @@
<field name="description_motivational" type="html">
<div class="media align-items-center">
<div class="media-body">Reach the next rank and become a Master!</div>
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="gamification/static/img/rank_master_badge.svg"/>
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_master_badge.svg"/>
</div>
</field>
<field name="karma_min">500</field>
@@ -54,7 +54,7 @@
<field name="description_motivational" type="html">
<div class="media align-items-center">
<div class="media-body">Reach the next rank and become a powerful user!</div>
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="gamification/static/img/rank_doctor_badge.svg"/>
<img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_doctor_badge.svg"/>
</div>
</field>
<field name="karma_min">2000</field>
+1 -1
View File
@@ -55,10 +55,10 @@ class HrExpense(models.Model):
tax_ids = fields.Many2many('account.tax', 'expense_tax', 'expense_id', 'tax_id', string='Taxes', states={'done': [('readonly', True)], 'post': [('readonly', True)]})
untaxed_amount = fields.Float("Subtotal", store=True, compute='_compute_amount', digits=dp.get_precision('Account'))
total_amount = fields.Monetary("Total", compute='_compute_amount', store=True, currency_field='currency_id', digits=dp.get_precision('Account'))
company_currency_id = fields.Many2one('res.currency', string="Report Company Currency", related='sheet_id.currency_id', store=True, readonly=False)
total_amount_company = fields.Monetary("Total (Company Currency)", compute='_compute_total_amount_company', store=True, currency_field='company_currency_id', digits=dp.get_precision('Account'))
company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.user.company_id)
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id)
company_currency_id = fields.Many2one('res.currency', string="Report Company Currency", related='sheet_id.currency_id', store=True, readonly=False)
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, oldname='analytic_account')
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags', states={'post': [('readonly', True)], 'done': [('readonly', True)]})
account_id = fields.Many2one('account.account', string='Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, default=_default_account_id, help="An expense account is expected")
@@ -16,6 +16,7 @@
<record id="holiday_status_hw" model="hr.leave.type">
<field name="name">Home Workings</field>
<field name="color_name">lightgreen</field>
<field name="validity_start" eval="False"/>
<field name="leave_notif_subtype_id" eval="ref('mt_leave_home_working')"/>
</record>
@@ -8,6 +8,7 @@
<field name="allocation_type">fixed_allocation</field>
<field name="color_name">blue</field>
<field name="request_unit">hour</field>
<field name="validity_start" eval="False"/>
</record>
<record id="hr_holiday_status_dv" model="hr.leave.type">
@@ -15,6 +16,7 @@
<field name="allocation_type">fixed</field>
<field name="color_name">brown</field>
<field name="validation_type">both</field>
<field name="validity_start" eval="False"/>
</record>
<record id="holiday_status_cla" model="hr.leave.type">
+1 -1
View File
@@ -168,7 +168,7 @@ class Employee(models.Model):
employee.leave_date_to = leave_data.get(employee.id, {}).get('leave_date_to')
employee.current_leave_state = leave_data.get(employee.id, {}).get('current_leave_state')
employee.current_leave_id = leave_data.get(employee.id, {}).get('current_leave_id')
employee.is_absent = leave_data and leave_data.get(employee.id, {}).get('current_leave_state') not in ['cancel', 'refuse', 'draft']
employee.is_absent = leave_data.get(employee.id) and leave_data.get(employee.id, {}).get('current_leave_state') not in ['cancel', 'refuse', 'draft']
@api.onchange('parent_id')
def _onchange_parent_id(self):
+14 -5
View File
@@ -522,16 +522,25 @@ class HolidaysRequest(models.Model):
@api.constrains('holiday_status_id', 'date_to', 'date_from')
def _check_leave_type_validity(self):
for leave in self:
vstart = leave.holiday_status_id.validity_start
vstop = leave.holiday_status_id.validity_stop
dfrom = leave.date_from
dto = leave.date_to
if leave.holiday_status_id.validity_start and leave.holiday_status_id.validity_stop:
vstart = leave.holiday_status_id.validity_start
vstop = leave.holiday_status_id.validity_stop
dfrom = leave.date_from
dto = leave.date_to
if dfrom and dto and (dfrom.date() < vstart or dto.date() > vstop):
raise UserError(
_('You can take %s only between %s and %s') % (
leave.holiday_status_id.display_name, leave.holiday_status_id.validity_start, leave.holiday_status_id.validity_stop))
elif leave.holiday_status_id.validity_start:
if dfrom and (dfrom.date() < vstart):
raise UserError(
_('You can take %s from %s') % (
leave.holiday_status_id.display_name, leave.holiday_status_id.validity_start))
elif leave.holiday_status_id.validity_stop:
if dto and (dto.date() > vstop):
raise UserError(
_('You can take %s until %s') % (
leave.holiday_status_id.display_name, leave.holiday_status_id.validity_stop))
@api.model
def create(self, values):
+1 -1
View File
@@ -183,7 +183,7 @@ class HolidaysType(models.Model):
if request.leave_type_request_unit == 'hour'
else request.number_of_days)
for allocation in allocations:
for allocation in allocations.sudo():
status_dict = result[allocation.holiday_status_id.id]
if allocation.state == 'validate':
# note: add only validated allocation even for the virtual
@@ -19,6 +19,7 @@ class TestLeavesRights(TestHrHolidaysBase):
'name': 'Unlimited',
'validation_type': 'hr',
'allocation_type': 'no',
'validity_start': False,
})
self.rd_dept.manager_id = False
self.hr_dept.manager_id = False
@@ -20,12 +20,14 @@ class TestAccrualAllocations(TestHrHolidaysBase):
self.accrual_type = LeaveType.create({
'name': 'accrual',
'allocation_type': 'fixed',
'validity_start': False,
})
self.unpaid_type = LeaveType.create({
'name': 'unpaid',
'allocation_type': 'no',
'unpaid': True,
'validity_start': False,
})
self.set_employee_create_date(self.employee_emp_id, '2010-02-03 00:00:00')
@@ -18,6 +18,7 @@ class TestChangeDepartment(TestHrHolidaysBase):
'name': 'NotLimitedHR',
'allocation_type': 'no',
'validation_type': 'hr',
'validity_start': False,
})
def create_holiday(name, start, end):
@@ -29,11 +29,13 @@ class TestHolidaysFlow(TestHrHolidaysBase):
'name': 'NotLimitedHR',
'allocation_type': 'no',
'validation_type': 'hr',
'validity_start': False,
})
self.holidays_status_manager = HolidayStatusManagerGroup.create({
'name': 'NotLimitedManager',
'allocation_type': 'no',
'validation_type': 'manager',
'validity_start': False,
})
HolidaysEmployeeGroup = Requests.sudo(self.user_employee_id)
@@ -95,13 +97,15 @@ class TestHolidaysFlow(TestHrHolidaysBase):
HolidayStatusManagerGroup.create({
'name': 'WithMeetingType',
'allocation_type': 'no',
'categ_id': self.env['calendar.event.type'].sudo(self.user_hrmanager_id).create({'name': 'NotLimitedMeetingType'}).id
'categ_id': self.env['calendar.event.type'].sudo(self.user_hrmanager_id).create({'name': 'NotLimitedMeetingType'}).id,
'validity_start': False,
})
self.holidays_status_limited = HolidayStatusManagerGroup.create({
'name': 'Limited',
'allocation_type': 'fixed',
'validation_type': 'both',
'validity_start': False,
})
HolidaysEmployeeGroup = Requests.sudo(self.user_employee_id)
@@ -16,6 +16,7 @@ class TestHrLeaveType(TestHrHolidaysBase):
'name': 'Paid Time Off',
'time_type': 'leave',
'allocation_type': 'no',
'validity_start': False,
})
leave_1 = self.env['hr.leave'].create({
@@ -32,11 +32,13 @@ class TestLeaveRequests(TestHrHolidaysBase):
'name': 'NotLimitedHR',
'allocation_type': 'no',
'validation_type': 'hr',
'validity_start': False,
})
self.holidays_type_2 = LeaveType.create({
'name': 'Limited',
'allocation_type': 'fixed',
'validation_type': 'hr',
'validity_start': False,
})
self.holidays_type_3 = LeaveType.create({
'name': 'TimeNotLimited',
@@ -173,3 +175,18 @@ class TestLeaveRequests(TestHrHolidaysBase):
allocation_form = Form(self.env['hr.leave.allocation'].sudo(self.user_employee))
allocation_form.holiday_status_id = self.holidays_type_1
allocation = allocation_form.save()
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_employee_is_absent(self):
""" Only the concerned employee should be considered absent """
self.env['hr.leave'].sudo(self.user_employee_id).create({
'name': 'Hol11',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.holidays_type_1.id,
'date_from': (fields.Datetime.now() - relativedelta(days=1)),
'date_to': fields.Datetime.now() + relativedelta(days=1),
'number_of_days': 2,
})
(self.employee_emp | self.employee_hrmanager).mapped('is_absent') # compute in batch
self.assertTrue(self.employee_emp.is_absent, "He should be considered absent")
self.assertFalse(self.employee_hrmanager.is_absent, "He should not be considered absent")
@@ -17,6 +17,7 @@ class TestOutOfOffice(TestHrHolidaysBase):
'name': 'Legal Leaves',
'time_type': 'leave',
'allocation_type': 'no',
'validity_start': False,
})
def test_leave_ooo(self):
@@ -87,6 +88,7 @@ class TestOutOfOfficePerformance(TestHrHolidaysBase):
'name': 'Legal Leaves',
'time_type': 'leave',
'allocation_type': 'no',
'validity_start': False,
})
self.leave_date_end = (datetime.today() + relativedelta(days=3))
self.leave = self.env['hr.leave'].create({
@@ -1,6 +1,6 @@
"id","name","code","user_type_id/id","chart_template_id/id","reconcile"
"chart1141_en","Stock In Hand","1141","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","False"
"chart1145_en","Stock Delivered But Not Billed","1145","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","False"
"chart1145_en","Stock Delivered But Not Billed","1145","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","True"
"chart1151_en","Customers Account","1151","account.data_account_type_receivable","l10n_ca.ca_en_chart_template_en","True"
"chart1181_en","GST receivable","1181","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","False"
"chart1182_en","PST/QST receivable","1182","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","False"
@@ -27,7 +27,7 @@
"chart21424_en","Labour Health and Safety to pay","21424","account.data_account_type_current_liabilities","l10n_ca.ca_en_chart_template_en","False"
"chart21425_en","Labour Standards to pay","21425","account.data_account_type_current_liabilities","l10n_ca.ca_en_chart_template_en","False"
"chart21426_en","Provincial Income Tax","21426","account.data_account_type_current_liabilities","l10n_ca.ca_en_chart_template_en","False"
"chart2171_en","Stock Received But Not Billed","2171","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","False"
"chart2171_en","Stock Received But Not Billed","2171","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","True"
"chart2181_en","Salaries to pay","2181","account.data_account_type_current_liabilities","l10n_ca.ca_en_chart_template_en","False"
"chart2183_en","Bonus to pay","2183","account.data_account_type_current_liabilities","l10n_ca.ca_en_chart_template_en","False"
"chart2184_en","Retroactive Payment to pay","2184","account.data_account_type_current_liabilities","l10n_ca.ca_en_chart_template_en","False"
1 id name code user_type_id/id chart_template_id/id reconcile
2 chart1141_en Stock In Hand 1141 account.data_account_type_current_assets l10n_ca.ca_en_chart_template_en False
3 chart1145_en Stock Delivered But Not Billed 1145 account.data_account_type_current_assets l10n_ca.ca_en_chart_template_en False True
4 chart1151_en Customers Account 1151 account.data_account_type_receivable l10n_ca.ca_en_chart_template_en True
5 chart1181_en GST receivable 1181 account.data_account_type_current_assets l10n_ca.ca_en_chart_template_en False
6 chart1182_en PST/QST receivable 1182 account.data_account_type_current_assets l10n_ca.ca_en_chart_template_en False
27 chart21424_en Labour Health and Safety to pay 21424 account.data_account_type_current_liabilities l10n_ca.ca_en_chart_template_en False
28 chart21425_en Labour Standards to pay 21425 account.data_account_type_current_liabilities l10n_ca.ca_en_chart_template_en False
29 chart21426_en Provincial Income Tax 21426 account.data_account_type_current_liabilities l10n_ca.ca_en_chart_template_en False
30 chart2171_en Stock Received But Not Billed 2171 account.data_account_type_current_assets l10n_ca.ca_en_chart_template_en False True
31 chart2181_en Salaries to pay 2181 account.data_account_type_current_liabilities l10n_ca.ca_en_chart_template_en False
32 chart2183_en Bonus to pay 2183 account.data_account_type_current_liabilities l10n_ca.ca_en_chart_template_en False
33 chart2184_en Retroactive Payment to pay 2184 account.data_account_type_current_liabilities l10n_ca.ca_en_chart_template_en False
@@ -21,7 +21,7 @@ do_niif_11050200,11050200,Inventario de Materia Prima,account.data_account_type_
do_niif_11050300,11050300,Inventario en Tránsito,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1105",l10n_do.account_group_1105
do_niif_11050400,11050400,Inventario de Materiales y Suministros,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1105",l10n_do.account_group_1105
do_niif_11050500,11050500,Inventario de Combustibles,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1105",l10n_do.account_group_1105
do_niif_11050600,11050600,Bienes Enviados No Facturados,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1105",l10n_do.account_group_1105
do_niif_11050600,11050600,Bienes Enviados No Facturados,account.data_account_type_current_assets,TRUE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1105",l10n_do.account_group_1105
do_niif_11060100,11060100,Deterioro Acum. de Inventario en Almacen,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1106",l10n_do.account_group_1106
do_niif_11060200,11060200,Deterioro Acum. de Materiales y Suministros,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1106",l10n_do.account_group_1106
do_niif_11060300,11060300,Deterioro Acum. de Combustibles,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1106",l10n_do.account_group_1106
@@ -99,7 +99,7 @@ do_niif_21020800,21020800,Aporte a Fondo de Pensiones (AFP) por Pagar,account.da
do_niif_21020900,21020900,Seguro de Riesgo Laboral (SRL) por Pagar,account.data_account_type_non_current_liabilities,FALSE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2102",l10n_do.account_group_2102
do_niif_21021000,21021000,INFOTEP por Pagar,account.data_account_type_payable,TRUE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2102",l10n_do.account_group_2102
do_niif_21021100,21021100,Tesorería de la Seguridad Social (TSS) por Pagar,account.data_account_type_payable,TRUE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2102",l10n_do.account_group_2102
do_niif_21021200,21021200,Bienes Recibidos no Facturados,account.data_account_type_non_current_liabilities,FALSE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2102",l10n_do.account_group_2102
do_niif_21021200,21021200,Bienes Recibidos no Facturados,account.data_account_type_non_current_liabilities,TRUE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2102",l10n_do.account_group_2102
do_niif_21030101,21030101,ITBIS por Venta Servicios,account.data_account_type_non_current_liabilities,FALSE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2103,account_tag_210301",l10n_do.account_group_210301
do_niif_21030102,21030102,ITBIS por Venta Bienes,account.data_account_type_non_current_liabilities,FALSE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2103,account_tag_210301",l10n_do.account_group_210301
do_niif_21030103,21030103,ITBIS por Venta Servicios en Nombre de Terceros,account.data_account_type_non_current_liabilities,FALSE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2103,account_tag_210301",l10n_do.account_group_210301
1 id code name user_type_id/id reconcile chart_template_id/id tag_ids/id group_id/id
21 do_niif_11050300 11050300 Inventario en Tránsito account.data_account_type_current_assets FALSE do_chart_template account_tag_1,account_tag_11,account_tag_1105 l10n_do.account_group_1105
22 do_niif_11050400 11050400 Inventario de Materiales y Suministros account.data_account_type_current_assets FALSE do_chart_template account_tag_1,account_tag_11,account_tag_1105 l10n_do.account_group_1105
23 do_niif_11050500 11050500 Inventario de Combustibles account.data_account_type_current_assets FALSE do_chart_template account_tag_1,account_tag_11,account_tag_1105 l10n_do.account_group_1105
24 do_niif_11050600 11050600 Bienes Enviados No Facturados account.data_account_type_current_assets FALSE TRUE do_chart_template account_tag_1,account_tag_11,account_tag_1105 l10n_do.account_group_1105
25 do_niif_11060100 11060100 Deterioro Acum. de Inventario en Almacen account.data_account_type_current_assets FALSE do_chart_template account_tag_1,account_tag_11,account_tag_1106 l10n_do.account_group_1106
26 do_niif_11060200 11060200 Deterioro Acum. de Materiales y Suministros account.data_account_type_current_assets FALSE do_chart_template account_tag_1,account_tag_11,account_tag_1106 l10n_do.account_group_1106
27 do_niif_11060300 11060300 Deterioro Acum. de Combustibles account.data_account_type_current_assets FALSE do_chart_template account_tag_1,account_tag_11,account_tag_1106 l10n_do.account_group_1106
99 do_niif_21020900 21020900 Seguro de Riesgo Laboral (SRL) por Pagar account.data_account_type_non_current_liabilities FALSE do_chart_template account_tag_2,account_tag_21,account_tag_2102 l10n_do.account_group_2102
100 do_niif_21021000 21021000 INFOTEP por Pagar account.data_account_type_payable TRUE do_chart_template account_tag_2,account_tag_21,account_tag_2102 l10n_do.account_group_2102
101 do_niif_21021100 21021100 Tesorería de la Seguridad Social (TSS) por Pagar account.data_account_type_payable TRUE do_chart_template account_tag_2,account_tag_21,account_tag_2102 l10n_do.account_group_2102
102 do_niif_21021200 21021200 Bienes Recibidos no Facturados account.data_account_type_non_current_liabilities FALSE TRUE do_chart_template account_tag_2,account_tag_21,account_tag_2102 l10n_do.account_group_2102
103 do_niif_21030101 21030101 ITBIS por Venta Servicios account.data_account_type_non_current_liabilities FALSE do_chart_template account_tag_2,account_tag_21,account_tag_2103,account_tag_210301 l10n_do.account_group_210301
104 do_niif_21030102 21030102 ITBIS por Venta Bienes account.data_account_type_non_current_liabilities FALSE do_chart_template account_tag_2,account_tag_21,account_tag_2103,account_tag_210301 l10n_do.account_group_210301
105 do_niif_21030103 21030103 ITBIS por Venta Servicios en Nombre de Terceros account.data_account_type_non_current_liabilities FALSE do_chart_template account_tag_2,account_tag_21,account_tag_2103,account_tag_210301 l10n_do.account_group_210301
@@ -1,2 +1,2 @@
id,property_account_receivable_id/id,property_account_payable_id/id,property_account_expense_categ_id/id,property_account_income_categ_id/id,income_currency_exchange_account_id/id,expense_currency_exchange_account_id/id
mn_chart_1,account_template_1201_0201,account_template_3101_0201,account_template_7002_1101,account_template_5101_0201,account_template_5301_0201,account_template_5302_0201
id,property_account_receivable_id/id,property_account_payable_id/id,property_account_expense_categ_id/id,property_account_income_categ_id/id,income_currency_exchange_account_id/id,expense_currency_exchange_account_id/id,property_stock_account_input_categ_id/id,property_stock_account_output_categ_id/id,property_stock_valuation_account_id/id
mn_chart_1,account_template_1201_0201,account_template_3101_0201,account_template_6101_0101,account_template_5101_0101,account_template_5301_0201,account_template_5302_0201,account_template_1407_0101,account_template_1408_0101,account_template_1401_0101
1 id property_account_receivable_id/id property_account_payable_id/id property_account_expense_categ_id/id property_account_income_categ_id/id income_currency_exchange_account_id/id expense_currency_exchange_account_id/id property_stock_account_input_categ_id/id property_stock_account_output_categ_id/id property_stock_valuation_account_id/id
2 mn_chart_1 account_template_1201_0201 account_template_3101_0201 account_template_7002_1101 account_template_6101_0101 account_template_5101_0201 account_template_5101_0101 account_template_5301_0201 account_template_5302_0201 account_template_1407_0101 account_template_1408_0101 account_template_1401_0101
@@ -10,6 +10,7 @@
<field name="transfer_account_code_prefix">1109</field>
<field name="code_digits">8</field>
<field name="currency_id" ref="base.MNT"/>
<field name="use_anglo_saxon" eval="True"/>
</record>
</odoo>
@@ -81,8 +81,8 @@
"ua_psbp_27",27,"Продукція сільськогосподарського виробництва","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
"ua_psbp_280",280,"Транспортно-заготівельні витрати","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
"ua_psbp_281",281,"Товари на складі","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
"ua_psbp_2811",2811,"Товари відвантажені зі складу","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
"ua_psbp_2812",2812,"Товари отримані на склад","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
"ua_psbp_2811",2811,"Товари відвантажені зі складу","account.data_account_type_current_assets",TRUE,"l10n_ua_psbo_chart_template"
"ua_psbp_2812",2812,"Товари отримані на склад","account.data_account_type_current_liabilities",TRUE,"l10n_ua_psbo_chart_template"
"ua_psbp_282",282,"Товари в торгівлі (за продажною вартістю)","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
"ua_psbp_283",283,"Товари на комісії","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
"ua_psbp_284",284,"Тара під товарами","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
@@ -350,7 +350,7 @@
"ua_ias_1101",1101,"Незавершене виробництво","account.data_account_type_current_assets",FALSE,"l10n_ua_ias_chart_template"
"ua_ias_1102",1102,"Готова продукція","account.data_account_type_current_assets",FALSE,"l10n_ua_ias_chart_template"
"ua_ias_1120",1120,"Дебіторська заборгованість за товари та послуги","account.data_account_type_receivable",TRUE,"l10n_ua_ias_chart_template"
"ua_ias_1121",1121,"Відвантажено зі складу без рахунку-фактури","account.data_account_type_current_assets",FALSE,"l10n_ua_ias_chart_template"
"ua_ias_1121",1121,"Відвантажено зі складу без рахунку-фактури","account.data_account_type_current_assets",TRUE,"l10n_ua_ias_chart_template"
"ua_ias_1130",1130,"Аванси партнерам","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
"ua_ias_1131",1131,"Інші оборотні активи","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
"ua_ias_1140",1140,"Податковий кредит по ПДВ","account.data_account_type_current_assets",FALSE,"l10n_ua_ias_chart_template"
@@ -363,7 +363,7 @@
"ua_ias_1170",1170,"Довгострокова дебіторська заборгованість","account.data_account_type_non_current_assets",FALSE,"l10n_ua_ias_chart_template"
"ua_ias_1180",1180,"Відстрочені податкові активи","account.data_account_type_non_current_assets",FALSE,"l10n_ua_ias_chart_template"
"ua_ias_1200",1200,"Кредиторська заборгованість за товари та послуги","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
"ua_ias_1201",1201,"Отримано на склад без рахунку-фактури","account.data_account_type_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
"ua_ias_1201",1201,"Отримано на склад без рахунку-фактури","account.data_account_type_current_liabilities",TRUE,"l10n_ua_ias_chart_template"
"ua_ias_1202",1202,"Податок на прибуток","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
"ua_ias_1203",1203,"ПДВ","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
"ua_ias_1204",1204,"Податкові зобов’язання по ПДВ","account.data_account_type_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
1 id code name user_type_id:id reconcile chart_template_id:id
81 ua_psbp_27 27 Продукція сільськогосподарського виробництва account.data_account_type_current_assets FALSE l10n_ua_psbo_chart_template
82 ua_psbp_280 280 Транспортно-заготівельні витрати account.data_account_type_current_assets FALSE l10n_ua_psbo_chart_template
83 ua_psbp_281 281 Товари на складі account.data_account_type_current_assets FALSE l10n_ua_psbo_chart_template
84 ua_psbp_2811 2811 Товари відвантажені зі складу account.data_account_type_current_assets FALSE TRUE l10n_ua_psbo_chart_template
85 ua_psbp_2812 2812 Товари отримані на склад account.data_account_type_current_liabilities FALSE TRUE l10n_ua_psbo_chart_template
86 ua_psbp_282 282 Товари в торгівлі (за продажною вартістю) account.data_account_type_current_assets FALSE l10n_ua_psbo_chart_template
87 ua_psbp_283 283 Товари на комісії account.data_account_type_current_assets FALSE l10n_ua_psbo_chart_template
88 ua_psbp_284 284 Тара під товарами account.data_account_type_current_assets FALSE l10n_ua_psbo_chart_template
350 ua_ias_1101 1101 Незавершене виробництво account.data_account_type_current_assets FALSE l10n_ua_ias_chart_template
351 ua_ias_1102 1102 Готова продукція account.data_account_type_current_assets FALSE l10n_ua_ias_chart_template
352 ua_ias_1120 1120 Дебіторська заборгованість за товари та послуги account.data_account_type_receivable TRUE l10n_ua_ias_chart_template
353 ua_ias_1121 1121 Відвантажено зі складу без рахунку-фактури account.data_account_type_current_assets FALSE TRUE l10n_ua_ias_chart_template
354 ua_ias_1130 1130 Аванси партнерам account.data_account_type_payable TRUE l10n_ua_ias_chart_template
355 ua_ias_1131 1131 Інші оборотні активи account.data_account_type_payable TRUE l10n_ua_ias_chart_template
356 ua_ias_1140 1140 Податковий кредит по ПДВ account.data_account_type_current_assets FALSE l10n_ua_ias_chart_template
363 ua_ias_1170 1170 Довгострокова дебіторська заборгованість account.data_account_type_non_current_assets FALSE l10n_ua_ias_chart_template
364 ua_ias_1180 1180 Відстрочені податкові активи account.data_account_type_non_current_assets FALSE l10n_ua_ias_chart_template
365 ua_ias_1200 1200 Кредиторська заборгованість за товари та послуги account.data_account_type_payable TRUE l10n_ua_ias_chart_template
366 ua_ias_1201 1201 Отримано на склад без рахунку-фактури account.data_account_type_current_liabilities FALSE TRUE l10n_ua_ias_chart_template
367 ua_ias_1202 1202 Податок на прибуток account.data_account_type_payable TRUE l10n_ua_ias_chart_template
368 ua_ias_1203 1203 ПДВ account.data_account_type_payable TRUE l10n_ua_ias_chart_template
369 ua_ias_1204 1204 Податкові зобов’язання по ПДВ account.data_account_type_current_liabilities FALSE l10n_ua_ias_chart_template
+1 -3
View File
@@ -187,9 +187,7 @@ class Channel(models.Model):
@api.onchange('public')
def _onchange_public(self):
if self.public == 'public':
self.alias_contact = 'everyone'
else:
if self.public != 'public' and self.alias_contact == 'everyone':
self.alias_contact = 'followers'
@api.onchange('moderator_ids')
+5 -1
View File
@@ -437,7 +437,11 @@ var Chatter = Widget.extend({
*/
_updateAttachmentCounter: function () {
var count = this.record.data.message_attachment_count || 0;
this.$('.o_chatter_attachment_button_count').html(count);
var $element = this.$('.o_chatter_attachment_button_count');
if (Number($element.html()) !== count) {
this._areAttachmentsLoaded = false;
$element.html(count);
}
},
/**
* @private
+3 -2
View File
@@ -16,8 +16,9 @@ FormView.include({
init: function () {
this._super.apply(this, arguments);
if ('message_ids' in this.fieldsInfo[this.viewType]) {
this.fieldsInfo[this.viewType].message_attachment_count = {};
var fieldsInfo = this.fieldsInfo[this.viewType];
if ('message_ids' in fieldsInfo && !('message_attachment_count' in fieldsInfo)) {
fieldsInfo.message_attachment_count = {};
}
},
});
+53
View File
@@ -242,6 +242,59 @@ QUnit.test('basic rendering', async function (assert) {
form.destroy();
});
QUnit.test('basic rendering: message_attachment_count can be in view standalone', async function (assert) {
assert.expect(1);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
services: this.services,
arch: '<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="message_attachment_count" string="I\'m here"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 2,
});
assert.strictEqual(form.$('.o_form_label').text(), "I'm here",
"The field message_attachment_count must be present according to the view's specs");
form.destroy();
});
QUnit.test('basic rendering: message_attachment_count can be in view with chatter', async function (assert) {
assert.expect(1);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
services: this.services,
arch: '<form string="Partners">' +
'<sheet>' +
'<group>' +
'<field name="message_attachment_count" string="I\'m here"/>' +
'</group>' +
'</sheet>' +
'<div class="oe_chatter">' +
'<field name="message_follower_ids" widget="mail_followers"/>' +
'<field name="message_ids" widget="mail_thread"/>' +
'<field name="activity_ids" widget="mail_activity"/>' +
'</div>' +
'</form>',
res_id: 2,
});
assert.strictEqual(form.$('.o_form_label').text(), "I'm here",
"The field message_attachment_count must be present according to the view's specs");
form.destroy();
});
QUnit.test('Activity Done keep feedback on blur', async function (assert) {
assert.expect(3);
var done = assert.async();
+1
View File
@@ -9,6 +9,7 @@
<field name="padding">5</field>
<field name="number_next">1</field>
<field name="number_increment">1</field>
<field name="company_id" eval="False"/>
</record>
<record id="sequence_mrp_unbuild" model="ir.sequence">
+3 -4
View File
@@ -506,6 +506,9 @@ class MrpProduction(models.Model):
factor = production.product_uom_id._compute_quantity(production.product_qty, production.bom_id.product_uom_id) / production.bom_id.product_qty
boms, lines = production.bom_id.explode(production.product_id, factor, picking_type=production.bom_id.picking_type_id)
for bom_line, line_data in lines:
if bom_line.child_bom_id and bom_line.child_bom_id.type == 'phantom' or\
bom_line.product_id.type not in ['product', 'consu']:
continue
moves.append(production._get_move_raw_values(bom_line, line_data))
return moves
@@ -513,10 +516,6 @@ class MrpProduction(models.Model):
quantity = line_data['qty']
# alt_op needed for the case when you explode phantom bom and all the lines will be consumed in the operation given by the parent bom line
alt_op = line_data['parent_line'] and line_data['parent_line'].operation_id.id or False
if bom_line.child_bom_id and bom_line.child_bom_id.type == 'phantom':
return self.env['stock.move']
if bom_line.product_id.type not in ['product', 'consu']:
return self.env['stock.move']
source_location = self.location_src_id
data = {
'sequence': bom_line.sequence,
+33
View File
@@ -989,3 +989,36 @@ class TestMrpOrder(TestMrpCommon):
self.assertEqual(product_produce.raw_workorder_line_ids[0].product_uom_id, unit, 'Should be the product uom so "unit"')
self.assertEqual(product_produce.raw_workorder_line_ids[1].qty_to_consume, 1, 'Should be 1 unit since the tracking is serial and quantity 2.')
self.assertEqual(product_produce.raw_workorder_line_ids[1].product_uom_id, unit, 'should be the product uom so "unit"')
def test_product_type_service_1(self):
# Create finished product
finished_product = self.env['product.product'].create({
'name': 'Geyser',
'type': 'product',
})
# Create service type product
product_raw = self.env['product.product'].create({
'name': 'raw Geyser',
'type': 'service',
})
# Create bom for finish product
bom = self.env['mrp.bom'].create({
'product_id': finished_product.id,
'product_tmpl_id': finished_product.product_tmpl_id.id,
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
'product_qty': 1.0,
'type': 'normal',
'bom_line_ids': [(5, 0), (0, 0, {'product_id': product_raw.id})]
})
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = finished_product
mo_form.bom_id = bom
mo_form.product_uom_id = self.env.ref('uom.product_uom_unit')
mo_form.product_qty = 1
mo = mo_form.save()
# Check Mo is created or not
self.assertTrue(mo, "Mo is created")
@@ -57,6 +57,9 @@ class ChangeProductionQty(models.TransientModel):
boms, lines = production.bom_id.explode(production.product_id, factor, picking_type=production.bom_id.picking_type_id)
documents = {}
for line, line_data in lines:
if line.child_bom_id and line.child_bom_id.type == 'phantom' or\
line.product_id.type not in ['product', 'consu']:
continue
move, old_qty, new_qty = production._update_raw_move(line, line_data)
iterate_key = production._get_document_iterate_key(move)
if iterate_key:
+1 -1
View File
@@ -881,7 +881,7 @@ class PaymentTransaction(models.Model):
values.update({
'partner_name': partner.name,
'partner_lang': partner.lang or 'en_US',
'partner_lang': partner.lang or self.env.user.lang,
'partner_email': partner.email,
'partner_zip': partner.zip,
'partner_address': _partner_format_address(partner.street or '', partner.street2 or ''),
+8 -2
View File
@@ -1566,6 +1566,7 @@ exports.Orderline = Backbone.Model.extend({
price_display : this.get_display_price(),
price_with_tax : this.get_price_with_tax(),
price_without_tax: this.get_price_without_tax(),
price_with_tax_before_discount: this.get_price_with_tax_before_discount(),
tax: this.get_tax(),
product_description: this.get_product().description,
product_description_sale: this.get_product().description_sale,
@@ -1629,9 +1630,9 @@ exports.Orderline = Backbone.Model.extend({
},
get_display_price_one: function(){
var rounding = this.pos.currency.rounding;
var price_unit = this.get_unit_price() * (1.0 - (this.get_discount() / 100.0));
var price_unit = this.get_unit_price();
if (this.pos.config.iface_tax_included !== 'total') {
return round_pr(price_unit, rounding);
return round_pr(price_unit * (1.0 - (this.get_discount() / 100.0)), rounding);
} else {
var product = this.get_product();
var taxes_ids = product.taxes_id;
@@ -1662,6 +1663,9 @@ exports.Orderline = Backbone.Model.extend({
get_price_with_tax: function(){
return this.get_all_prices().priceWithTax;
},
get_price_with_tax_before_discount: function () {
return this.get_all_prices().priceWithTaxBeforeDiscount;
},
get_tax: function(){
return this.get_all_prices().tax;
},
@@ -1907,6 +1911,7 @@ exports.Orderline = Backbone.Model.extend({
});
var all_taxes = this.compute_all(product_taxes, price_unit, this.get_quantity(), this.pos.currency.rounding);
var all_taxes_before_discount = this.compute_all(product_taxes, this.get_unit_price(), this.get_quantity(), this.pos.currency.rounding);
_(all_taxes.taxes).each(function(tax) {
taxtotal += tax.amount;
taxdetail[tax.id] = tax.amount;
@@ -1916,6 +1921,7 @@ exports.Orderline = Backbone.Model.extend({
"priceWithTax": all_taxes.total_included,
"priceWithoutTax": all_taxes.total_excluded,
"priceSumTaxVoid": all_taxes.total_void,
"priceWithTaxBeforeDiscount": all_taxes_before_discount.total_included,
"tax": taxtotal,
"taxDetails": taxdetail,
};
+7 -2
View File
@@ -791,8 +791,8 @@
<left><t t-esc='line.product_name_wrapped[0]' /></left>
<right><value t-att-value-decimals='pos.currency.decimals'><t t-esc='line.price_display' /></value></right>
</line>
<t t-call="XmlReceiptWrappedProductNameLines"/>
</t>
<t t-call="XmlReceiptWrappedProductNameLines"/>
<t t-if='!simple'>
<line><left><t t-esc='line.product_name_wrapped[0]' /></left></line>
<t t-call="XmlReceiptWrappedProductNameLines"/>
@@ -812,7 +812,12 @@
<line indent='1'>
<left>
<value t-att-value-decimals='pos.dp["Product Price"]'>
<t t-esc="line.price_lst" />
<t t-if="pos.config.iface_tax_included === 'total'">
<t t-esc="line.price_with_tax_before_discount" />
</t>
<t t-else="">
<t t-esc="line.price_lst" />
</t>
</value>
</left>
</line>
+2 -1
View File
@@ -28,7 +28,8 @@ class report_product_pricelist(models.AbstractModel):
}
def _get_quantity(self, data):
return sorted([data['form'][key] for key in data['form'] if key.startswith('qty') and data['form'][key]])
form = data and data.get('form') or {}
return sorted([form[key] for key in form if key.startswith('qty') and form[key]])
def _get_categories(self, pricelist, products, quantities):
categ_data = []
@@ -46,6 +46,7 @@ class TestTimesheetHolidays(TestCommonTimesheet):
'timesheet_generate': True,
'timesheet_project_id': self.internal_project.id,
'timesheet_task_id': self.internal_task_leaves.id,
'validity_start': False,
})
self.hr_leave_type_no_ts = self.env['hr.leave.type'].create({
'name': 'Leave Type without timesheet generation',
@@ -53,6 +54,7 @@ class TestTimesheetHolidays(TestCommonTimesheet):
'timesheet_generate': False,
'timesheet_project_id': False,
'timesheet_task_id': False,
'validity_start': False,
})
# HR Officer allocates some leaves to the employee 1
+3
View File
@@ -201,6 +201,9 @@ class SaleOrder(models.Model):
@api.depends('pricelist_id', 'date_order', 'company_id')
def _compute_currency_rate(self):
for order in self:
if not order.company_id:
order.currency_rate = order.currency_id.with_context(date=order.date_order).rate or 1.0
continue
order.currency_rate = self.env['res.currency']._get_conversion_rate(order.company_id.currency_id, order.currency_id, order.company_id, order.date_order)
def _compute_access_url(self):
@@ -1459,3 +1459,48 @@ class TestSaleMrpFlow(common.SavepointCase):
move_component_kg = order.picking_ids[0].move_lines - move_component_unit
self.assertEquals(move_component_unit.product_uom_qty, 0.5)
self.assertEquals(move_component_kg.product_uom_qty, 0.583)
def test_product_type_service_1(self):
route_manufacture = self.warehouse.manufacture_pull_id.route_id.id
route_mto = self.warehouse.mto_pull_id.route_id.id
self.uom_unit = self.env.ref('uom.product_uom_unit')
# Create finished product
finished_product = self.env['product.product'].create({
'name': 'Geyser',
'type': 'product',
'route_ids': [(4, route_mto), (4, route_manufacture)],
})
# Create service type product
product_raw = self.env['product.product'].create({
'name': 'raw Geyser',
'type': 'service',
})
# Create bom for finish product
bom = self.env['mrp.bom'].create({
'product_id': finished_product.id,
'product_tmpl_id': finished_product.product_tmpl_id.id,
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
'product_qty': 1.0,
'type': 'normal',
'bom_line_ids': [(5, 0), (0, 0, {'product_id': product_raw.id})]
})
# Create sale order
sale_form = Form(self.env['sale.order'])
sale_form.partner_id = self.env.ref('base.res_partner_1')
with sale_form.order_line.new() as line:
line.name = finished_product.name
line.product_id = finished_product
line.product_uom_qty = 1.0
line.product_uom = self.uom_unit
line.price_unit = 10.0
sale_order = sale_form.save()
sale_order.action_confirm()
mo = self.env['mrp.production'].search([('product_id', '=', finished_product.id)])
self.assertTrue(mo, 'Manufacturing order created.')
-26
View File
@@ -1,26 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * survey_crm
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~11.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-18 09:49+0000\n"
"PO-Revision-Date: 2018-08-24 09:26+0000\n"
"Language-Team: Azerbaijani (https://www.transifex.com/odoo/teams/41243/az/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: az\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: survey_crm
#: model:ir.model,name:survey_crm.model_survey_mail_compose_message
msgid "Email composition wizard for Survey"
msgstr ""
#. module: survey_crm
#: model:ir.actions.act_window,name:survey_crm.action_partner_survey_mail_crm
msgid "Partner Survey Mailing"
msgstr ""
+1 -1
View File
@@ -1048,7 +1048,7 @@ class Binary(http.Controller):
filename_field=filename_field, download=download, mimetype=mimetype,
default_mimetype='image/png', access_token=access_token)
if status == 301 or (status != 200 and download):
if status in [301, 304] or (status != 200 and download):
return request.env['ir.http']._response_by_status(status, headers, content)
if not content:
content = base64.b64encode(self.placeholder(image=placeholder))
+12 -12
View File
@@ -58,18 +58,18 @@ var pluralize = function (u) {
};
};
var months = [
'كانون الثاني يناير',
'شباط فبراير',
'آذار مارس',
'نيسان أبريل',
'أيار مايو',
'حزيران يونيو',
'تموز يوليو',
'آب أغسطس',
'أيلول سبتمبر',
'تشرين الأول أكتوبر',
'تشرين الثاني نوفمبر',
'كانون الأول ديسمبر'
'يناير',
'فبراير',
'مارس',
'أبريل',
'مايو',
'يونيو',
'يوليو',
'أغسطس',
'سبتمبر',
'أكتوبر',
'نوفمبر',
'ديسمبر'
];
var ar = moment.defineLocale('ar', {
@@ -12,6 +12,7 @@ var AddNewFavoriteMenu = Widget.extend({
events: _.extend({}, Widget.prototype.events, {
'click .o_save_favorite': '_onSaveFavoriteClick',
'click .o_add_favorite.o_menu_header': '_onMenuHeaderClick',
'click input[type="checkbox"]': '_onCheckboxClick',
'keyup .o_save_name input': '_onKeyUp',
}),
/**
@@ -107,6 +108,33 @@ var AddNewFavoriteMenu = Widget.extend({
// Handlers
//--------------------------------------------------------------------------
/**
* Additional behavior when a checkbox is clicked upon
* Namely, some checkbox are mutually exclusive
* this function allows this
*
* @private
* @param {jQueryEvent} ev
*/
_onCheckboxClick: function (ev) {
function mutuallyExclusive (elem) {
if (!elem.id) return false;
return ['use_by_default', 'share_all_users'].some(function (str) {
return elem.id.indexOf(str);
});
}
var $checkboxes = this.$('input[type="checkbox"]');
var currentCheckBox = ev.target;
if (mutuallyExclusive(currentCheckBox)) {
for (var i=0; i < $checkboxes.length; i++) {
var checkbox = $checkboxes[i];
if (checkbox !== currentCheckBox && mutuallyExclusive(checkbox)) {
checkbox.checked = false;
}
}
}
},
/**
* @private
* @param {jQueryEvent} ev
@@ -53,7 +53,6 @@ var PivotController = AbstractController.extend({
* @override
*/
start: function () {
this.$el.toggleClass('o_enable_linking', this.enableLinking);
this.$fieldSelection = this.$('.o_field_selection');
core.bus.on('click', this, function () {
this.$fieldSelection.empty();
@@ -283,7 +283,7 @@ var PivotRenderer = AbstractRenderer.extend({
$cell = $('<td>')
.data('id', rows[i].id)
.data('col_id', rows[i].col_ids[Math.floor(j / nbrMeasures)])
.toggleClass('o_empty', !value)
.toggleClass('o_empty', _.isUndefined(value))
.addClass('o_pivot_cell_value text-right');
if (value !== undefined) {
$cell.append($('<div>', {class: 'o_value'}).html(formatter(value, measure)));
@@ -34,6 +34,11 @@ var PieChart = Widget.extend({
title: node.attrs.title || modifiers.title || modifiers.measure,
});
var pieChartContext = JSON.parse(JSON.stringify(record.context));
delete pieChartContext.graph_mode;
delete pieChartContext.graph_measure;
delete pieChartContext.graph_groupbys;
this.subViewParams = {
modelName: record.model,
withControlPanel: false,
@@ -41,7 +46,7 @@ var PieChart = Widget.extend({
mode: 'pie',
};
this.subViewParams.searchQuery = {
context: record.context,
context: pieChartContext,
domain: domain,
groupBy: [],
};
@@ -87,4 +87,9 @@
right: 6px;
top: 9px;
}
.o_pivot_field_menu .dropdown-item.disabled {
color: $text-muted;
cursor: default;
}
}
+1 -1
View File
@@ -532,7 +532,7 @@
</div>
</div>
<div t-name="PivotView" class="o_pivot">
<div t-name="PivotView" t-attf-class="o_pivot #{widget.enableLinking ? 'o_enable_linking' : ''}">
<div class="o_field_selection"/>
</div>
<t t-name="PivotView.buttons">
@@ -418,6 +418,47 @@ QUnit.module('Views', {
controlPanel.destroy();
});
QUnit.test('Favorites Use by Default and Share are exclusive', async function (assert) {
assert.expect(11);
var controlPanel = await createControlPanel({
model: 'partner',
arch: "<search></search>",
data: this.data,
searchMenuTypes: ['favorite'],
});
testUtils.dom.click(controlPanel.$('.o_favorites_menu_button'));
testUtils.dom.click(controlPanel.$('button.o_add_favorite'));
var $checkboxes = controlPanel.$('input[type="checkbox"]');
assert.strictEqual($checkboxes.length, 2,
'2 checkboxes are present')
assert.notOk($checkboxes[0].checked, 'Start: None of the checkboxes are checked (1)');
assert.notOk($checkboxes[1].checked, 'Start: None of the checkboxes are checked (2)');
testUtils.dom.click($checkboxes.eq(0));
assert.ok($checkboxes[0].checked, 'The first checkbox is checked');
assert.notOk($checkboxes[1].checked, 'The second checkbox is not checked');
testUtils.dom.click($checkboxes.eq(1));
assert.notOk($checkboxes[0].checked,
'Clicking on the second checkbox checks it, and unchecks the first (1)');
assert.ok($checkboxes[1].checked,
'Clicking on the second checkbox checks it, and unchecks the first (2)');
testUtils.dom.click($checkboxes.eq(0));
assert.ok($checkboxes[0].checked,
'Clicking on the first checkbox checks it, and unchecks the second (1)');
assert.notOk($checkboxes[1].checked,
'Clicking on the first checkbox checks it, and unchecks the second (2)');
testUtils.dom.click($checkboxes.eq(0));
assert.notOk($checkboxes[0].checked, 'End: None of the checkboxes are checked (1)');
assert.notOk($checkboxes[1].checked, 'End: None of the checkboxes are checked (2)');
controlPanel.destroy();
});
QUnit.test('load filter', async function (assert) {
assert.expect(1);
+3 -3
View File
@@ -106,7 +106,7 @@ QUnit.module('Views', {
},
});
assert.hasClass(pivot.$el,'o_enable_linking',
assert.hasClass(pivot.$('.o_pivot'),'o_enable_linking',
"root node should have classname 'o_enable_linking'");
assert.strictEqual(pivot.$('td.o_pivot_cell_value:contains(32)').length, 1,
"should contain a pivot cell with the sum of all records");
@@ -269,7 +269,7 @@ QUnit.module('Views', {
}
});
assert.hasClass(pivot.$el,'o_enable_linking',
assert.hasClass(pivot.$('.o_pivot'),'o_enable_linking',
"root node should have classname 'o_enable_linking'");
await testUtils.dom.click(pivot.$('.o_pivot_cell_value:contains(12)')); // should trigger a do_action
@@ -293,7 +293,7 @@ QUnit.module('Views', {
},
});
assert.doesNotHaveClass(pivot.$el, 'o_enable_linking',
assert.doesNotHaveClass(pivot.$('.o_pivot'), 'o_enable_linking',
"root node should not have classname 'o_enable_linking'");
assert.containsOnce(pivot, '.o_pivot_cell_value',
"should have one cell");
+21
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import io
import base64
from PIL import Image
@@ -42,3 +43,23 @@ class TestImage(HttpCase):
response = self.url_open('/web/image/fake/0/image_no_size')
image = Image.open(io.BytesIO(response.content))
self.assertEqual(image.size, (256, 256))
def test_02_content_image_Etag_304(self):
"""This test makes sure that the 304 response is properly returned if the ETag is properly set"""
attachment = self.env['ir.attachment'].create({
'datas': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=",
'name': 'testEtag.gif',
'public': True,
'datas_fname': 'testEtag.gif',
'mimetype': 'image/gif',
})
response = self.url_open('/web/image/%s' % attachment.id)
self.assertEqual(response.status_code, 200)
self.assertEqual(base64.b64encode(response.content), attachment.datas)
etag = response.headers.get('ETag')
response2 = self.url_open('/web/image/%s' % attachment.id, headers={"If-None-Match": etag})
self.assertEqual(response2.status_code, 304)
self.assertEqual(len(response2.content), 0)
@@ -68,6 +68,8 @@ Wysiwyg.include({
}
$(document.body).off('.' + this.id);
this.$iframe.parents().removeClass('o_wysiwyg_no_transform');
this.$target.insertBefore(this.$iframe);
delete window.top[this._onUpdateIframeId];
@@ -100,6 +102,10 @@ Wysiwyg.include({
}
self.$iframe.toggleClass('o_fullscreen');
self.$iframe.contents().find('body').toggleClass('o_fullscreen');
// Hack to avoid a parent of the fullscreen iframe to have a
// transform (otherwise the position: fixed won't work)
self.$iframe.parents().toggleClass('o_wysiwyg_no_transform');
},
isFullscreen: function () {
if (!self.$iframe) {
@@ -10,6 +10,10 @@ iframe.wysiwyg_iframe.o_fullscreen {
border: 0;
}
.o_wysiwyg_no_transform {
transform: none !important;
}
body.o_in_iframe {
background-color: $o-view-background-color;
overflow: hidden;
@@ -9,10 +9,12 @@ $-compat-gray-map: (
'gray': '700',
'gray-light': '600',
'gray-lighter': '200',
);
) !default;
@each $old, $new in $-compat-gray-map {
@include bg-variant(".bg-#{$old}", gray($new));
@include text-emphasis-variant(".text-#{$old}", gray($new));
@if not map-has-key($grays, $old) {
@include bg-variant(".bg-#{$old}", gray($new));
@include text-emphasis-variant(".text-#{$old}", gray($new));
}
}
// Restore media ?
@@ -210,9 +212,13 @@ $-compat-breakpoints: (
.hide {
display: none !important;
}
.show {
display: block !important;
}
// The 'show' class could be supported if defined here and like that,
// unfortunately, BS4 still defines a 'show' class for other purposes which
// conflict with this (tab-pane, fade effects, ...). Adding more complex rules
// won't solve the problem as they would change css rules priorities.
// .show {
// display: block !important;
// }
.hidden {
display: none !important;
}
File diff suppressed because it is too large Load Diff
@@ -3,6 +3,8 @@
<template id="iframe_css_assets_edit" name="CSS assets for wysiwyg iframe content for popup">
<t t-call-assets="web.assets_common" t-js="false"/>
<t t-call-assets="web.assets_frontend" t-js="false"/>
<t t-call-assets="web_editor.assets_wysiwyg" t-js="false"/>
<t t-call-assets="website.assets_editor" t-js="false"/>
<link rel="stylesheet" href="/website_mass_mailing/static/src/css/website_mass_mailing_popup.css" type="text/css"/>
</template>
+4 -1
View File
@@ -608,9 +608,12 @@ class WebsiteSale(http.Controller):
new_values['customer'] = True
new_values['team_id'] = request.website.salesteam_id and request.website.salesteam_id.id
new_values['user_id'] = request.website.salesperson_id and request.website.salesperson_id.id
new_values['company_id'] = request.website.company_id.id
new_values['website_id'] = request.website.id
if order.partner_id.id == request.website.user_id.sudo().partner_id.id:
# If the partner is public, we assign the company of the website
new_values['company_id'] = request.website.company_id.id
lang = request.lang if request.lang in request.website.mapped('language_ids.code') else None
if lang:
new_values['lang'] = lang
@@ -1,6 +1,7 @@
odoo.define('website_sale_comparison.comparison', function (require) {
'use strict';
var concurrency = require('web.concurrency');
var core = require('web.core');
var publicWidget = require('web.public.widget');
var utils = require('web.utils');
@@ -30,6 +31,7 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, {
this.product_data = {};
this.comparelist_product_ids = JSON.parse(utils.get_cookie('comparelist_product_ids') || '[]');
this.product_compare_limit = 4;
this.guard = new concurrency.Mutex();
},
/**
* @override
@@ -154,6 +156,9 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, {
* @private
*/
_addNewProducts: function (product_id) {
this.guard.exec(this._addNewProductsImpl.bind(this, product_id));
},
_addNewProductsImpl: function (product_id) {
var self = this;
$('.o_product_feature_panel').addClass('d-md-block');
if (!_.contains(self.comparelist_product_ids, product_id)) {
@@ -161,8 +166,9 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, {
if (_.has(self.product_data, product_id)){
self._updateContent();
} else {
self._loadProducts([product_id]).then(function () {
return self._loadProducts([product_id]).then(function () {
self._updateContent();
self._updateCookie();
});
}
}
@@ -189,6 +195,9 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, {
* @private
*/
_removeFromComparelist: function (e) {
this.guard.exec(this._removeFromComparelistImpl.bind(this, e));
},
_removeFromComparelistImpl: function (e) {
this.comparelist_product_ids = _.without(this.comparelist_product_ids, $(e.currentTarget).data('product_product_id'));
$(e.currentTarget).parents('.o_product_row').remove();
this._updateCookie();
+21 -1
View File
@@ -379,6 +379,16 @@ class WebsiteSlides(WebsiteProfile):
step=self._slides_per_page, url_args=pager_args,
scope=page_count if page_count < self._pager_max_pages else self._pager_max_pages)
query_string = None
if category:
query_string = "?search_category=%s" % category.id
elif tag:
query_string = "?search_tag=%s" % tag.id
elif slide_type:
query_string = "?search_slide_type=%s" % slide_type
elif uncategorized:
query_string = "?search_uncategorized=1"
values = {
'channel': channel,
'active_tab': kw.get('active_tab', 'home'),
@@ -387,6 +397,7 @@ class WebsiteSlides(WebsiteProfile):
'search_tag': tag,
'search_slide_type': slide_type,
'search_uncategorized': uncategorized,
'query_string': query_string,
'slide_types': slide_types,
'sorting': actual_sorting,
'search': search,
@@ -506,7 +517,7 @@ class WebsiteSlides(WebsiteProfile):
@http.route('''/slides/slide/<model("slide.slide", "[('website_id', 'in', (False, current_website_id))]"):slide>''', type='http', auth="public", website=True)
def slide_view(self, slide, **kwargs):
if not slide.channel_id.can_access_from_current_website():
if not slide.channel_id.can_access_from_current_website() or not slide.active:
raise werkzeug.exceptions.NotFound()
self._set_viewed_slide(slide)
@@ -517,6 +528,15 @@ class WebsiteSlides(WebsiteProfile):
# sidebar: update with user channel progress
values['channel_progress'] = self._get_channel_progress(slide.channel_id, include_quiz=True)
# Allows to have breadcrumb for the previously used filter
values.update({
'search_category': slide.category_id.id if kwargs.get('search_category') else None,
'search_tag': request.env['slide.tag'].browse(int(kwargs.get('search_tag'))) if kwargs.get('search_tag') else None,
'slide_types': dict(request.env['slide.slide']._fields['slide_type']._description_selection(request.env)) if kwargs.get('search_slide_type') else None,
'search_slide_type': kwargs.get('search_slide_type'),
'search_uncategorized': kwargs.get('search_uncategorized')
})
if kwargs.get('fullscreen') == '1':
return request.render("website_slides.slide_fullscreen", values)
return request.render("website_slides.slide_main", values)
+11 -6
View File
@@ -135,7 +135,7 @@ class Slide(models.Model):
('video', 'Video'),
('quiz', "Quiz")],
string='Type', required=True,
default='document', readonly=True,
default='document',
help="The document type will be set automatically based on the document URL and properties (e.g. height and width for presentation and document).")
index_content = fields.Text('Transcript')
datas = fields.Binary('Content', attachment=True)
@@ -553,11 +553,16 @@ class Slide(models.Model):
return result
def _find_document_data_from_url(self, url):
expr = re.compile(r'^.*((youtu.be/)|(v/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*')
arg = expr.match(url)
document_id = arg and arg.group(7) or False
if document_id:
return ('youtube', document_id)
url_obj = urls.url_parse(url)
if url_obj.ascii_host == 'youtu.be':
return ('youtube', url_obj.path[1:] if url_obj.path else False)
elif url_obj.ascii_host in ('youtube.com', 'www.youtube.com', 'm.youtube.com'):
v_query_value = url_obj.decode_query().get('v')
if v_query_value:
return ('youtube', v_query_value)
split_path = url_obj.path.split('/')
if len(split_path) >= 3 and split_path[1] in ('v', 'embed'):
return ('youtube', split_path[2])
expr = re.compile(r'(^https:\/\/docs.google.com|^https:\/\/drive.google.com).*\/d\/([^\/]*)')
arg = expr.match(url)
@@ -5,6 +5,7 @@ odoo.define('website_slides.fullscreen', function (require) {
var publicWidget = require('web.public.widget');
var core = require('web.core');
var config = require('web.config');
var QWeb = core.qweb;
var session = require('web.session');
@@ -321,6 +322,9 @@ odoo.define('website_slides.fullscreen', function (require) {
start: function (){
var self = this;
this.on('change:slide', this, this._onChangeSlide);
if (config.device.size_class > config.device.SIZES.MD) {
this._toggleSidebar();
}
return this._super.apply(this, arguments).then(function () {
self._onChangeSlide(); // trigger manually once DOM ready, since slide content is not rendered server side
});
@@ -540,12 +544,20 @@ odoo.define('website_slides.fullscreen', function (require) {
this.sidebar.goNext();
},
/**
* Show or Hide the sidebar
* Called when the sidebar toggle is clicked -> toggles the sidebar visibility.
*
* @private
*/
_onClickToggleSidebar: function (ev){
ev.preventDefault();
this._toggleSidebar();
},
/**
* Toggles sidebar visibility.
*
* @private
*/
_toggleSidebar: function () {
this.$('.o_wslides_fs_sidebar').toggleClass('o_wslides_fs_sidebar_hidden');
this.$('.o_wslides_fs_toggle_sidebar').toggleClass('active');
},
@@ -49,48 +49,12 @@ publicWidget.registry.websiteSlidesShare = publicWidget.Widget.extend({
* @param {Object} parent
*/
start: function (parent) {
var self = this;
var defs = [this._super.apply(this, arguments)];
defs.push(new ShareMail(this).attachTo($('.oe_slide_js_share_email')));
if ($('div#statistic').length) {
this.slideURL = $('div#statistic').attr('slide-url');
this.socialURLs = {
linkedin: 'https://www.linkedin.com/countserv/count/share?url=',
twitter: 'https://cdn.api.twitter.com/1/urls/count.json?url=',
facebook: 'https://graph.facebook.com/?id=',
};
}
_.each(this.socialURLs, function (value, key) {
self._updateStatistics(key, self.slideURL);
});
return Promise.all(defs);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
* @param {string} socialSite
* @param {string} slide_url
*/
_updateStatistics: function (socialSite, slideURL) {
var self = this;
$.ajax({
url: self.socialURLs[socialSite] + slideURL,
dataType: 'jsonp',
success: function (data) {
var shareCount = (socialSite === 'facebook' ? data.shares : data.count) || 0;
$('#' + socialSite + '-badge').text(shareCount);
$('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#' + socialSite + '-badge').text()));
},
});
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
@@ -100,14 +64,11 @@ publicWidget.registry.websiteSlidesShare = publicWidget.Widget.extend({
* @param {Object} ev
*/
_onSlidesSocialShare: function (ev) {
var self = this;
ev.preventDefault();
var key = $(ev.currentTarget).attr('social-key');
var popUpURL = $(ev.currentTarget).attr('href');
var popUp = window.open(popUpURL, 'Share Dialog', 'width=626,height=436');
$(window).on('focus', function () {
if (popUp.closed) {
self._updateStatistics(key, self.slide_url);
$(window).off('focus');
}
});
+2
View File
@@ -5,3 +5,5 @@ from . import common
from . import test_security
from . import test_statistics
from . import test_karma
from . import test_from_url
@@ -0,0 +1,30 @@
import odoo.tests
class TestFromURL(odoo.tests.TransactionCase):
def test_youtube_urls(self):
urls = {
'W0JQcpGLSFw': [
'https://youtu.be/W0JQcpGLSFw',
'https://www.youtube.com/watch?v=W0JQcpGLSFw',
'https://www.youtube.com/watch?v=W0JQcpGLSFw&list=PL1-aSABtP6ACZuppkBqXFgzpNb2nVctZx',
],
'vmhB-pt7EfA': [ # id starts with v, it is important
'https://youtu.be/vmhB-pt7EfA',
'https://www.youtube.com/watch?feature=youtu.be&v=vmhB-pt7EfA',
'https://www.youtube.com/watch?v=vmhB-pt7EfA&list=PL1-aSABtP6ACZuppkBqXFgzpNb2nVctZx&index=7',
],
'hlhLv0GN1hA': [
'https://www.youtube.com/v/hlhLv0GN1hA',
'https://www.youtube.com/embed/hlhLv0GN1hA',
'https://m.youtube.com/watch?v=hlhLv0GN1hA'
],
}
model = self.env['slide.slide']
for id, urls in urls.items():
for url in urls:
with self.subTest(url=url, id=id):
document = model._find_document_data_from_url(url)
self.assertEqual(document[0], 'youtube')
self.assertEqual(document[1], id)
@@ -71,13 +71,13 @@
</page>
<page string="Document">
<group>
<field name="slide_type" readonly="1"/>
<field name="slide_type"/>
<field name="url"
attrs="{'required': [('slide_type', 'in', ('video'))], 'readonly': [('slide_type', 'in', ('document', 'video'))], 'invisible': [('slide_type', 'not in', ('document', 'presentation', 'video'))]}" />
attrs="{'required': [('slide_type', 'in', ('video'))], 'invisible': [('slide_type', 'not in', ('document', 'presentation', 'video'))]}" />
<field name="document_id" readonly="1" attrs="{'invisible': [('document_id', '=', False)]}"/>
<field name="mime_type" readonly="1"
attrs="{'invisible': [('slide_type', 'not in', ('document', 'presentation', 'infographic', 'webpage'))]}"/>
<field name="datas" readonly="1"
<field name="datas"
attrs="{'invisible': [('slide_type', 'not in', ('document', 'presentation', 'infographic', 'webpage'))]}"/>
</group>
</page>
@@ -93,6 +93,7 @@
<field name="slide_views"/>
<field name="public_views"/>
<field name="total_views"/>
<field name="completion_time" widget='float_time'/>
</group>
</group>
<label for="index_content"/>
@@ -7,30 +7,34 @@
<div class="container">
<div class="row align-items-center justify-content-between">
<!-- Desktop Mode -->
<nav aria-label="breadcrumb" class="col d-none d-md-flex">
<nav aria-label="breadcrumb" class="col-md-8 d-none d-md-flex">
<ol class="breadcrumb bg-transparent mb-0 pl-0 py-0">
<li class="breadcrumb-item">
<a href="/slides">Home</a>
</li>
<li t-att-class="'breadcrumb-item %s' % ('active' if not search_category and not search_tag and not search_slide_type else '')">
<t t-set="breadcrumb_class" t-value="'breadcrumb-item %s' % ('active' if not slide else '')" />
<li t-att-class="'breadcrumb-item %s' % ('active' if not search_category and not search_tag and not search_slide_type and not slide else '')">
<a t-att-href="'/slides/%s' % slug(channel)"><span t-esc="channel.name"/></a>
</li>
<li class="breadcrumb-item active" t-att-aria-current="'page' and search_category" t-if="search_category">
<li t-att-class="breadcrumb_class" t-att-aria-current="'page' and search_category" t-if="search_category">
<a t-att-href="'/slides/%s/category/%s' % (slug(channel), slug(search_category))"><span t-esc="search_category.name"/></a>
</li>
<li class="breadcrumb-item active" t-att-aria-current="'page' and search_tag" t-if="search_tag">
<li t-att-class="breadcrumb_class" t-att-aria-current="'page' and search_tag" t-if="search_tag">
<a t-att-href="'/slides/%s/tag/%s' % (slug(channel), slug(search_tag))"><span t-esc="search_tag.name"/></a>
</li>
<li class="breadcrumb-item active" t-att-aria-current="'page' and search_uncategorized" t-if="search_uncategorized">
<li t-att-class="breadcrumb_class" t-att-aria-current="'page' and search_uncategorized" t-if="search_uncategorized">
<a t-att-href="'/slides/%s?search_uncategorized=1' % (slug(channel))">Uncategorized</a>
</li>
<li class="breadcrumb-item active" t-att-aria-current="'page' and search_slide_type" t-if="search_slide_type">
<li t-att-class="breadcrumb_class" t-att-aria-current="'page' and search_slide_type" t-if="search_slide_type">
<a t-att-href="'/slides/%s?slide_type=%s' % (slug(channel), search_slide_type)"><span t-esc="slide_types[search_slide_type]"/></a>
</li>
<li t-if="slide" class="breadcrumb-item active">
<a t-att-href="'/slides/slide/%s' % slug(slide)"><span t-esc="slide.name"/></a>
</li>
</ol>
</nav>
<div class="col d-none d-md-flex flex-row align-items-center justify-content-end">
<div class="col-md-4 d-none d-md-flex flex-row align-items-center justify-content-end">
<!-- search -->
<form t-attf-action="/slides/all" role="search" method="get">
<div class="input-group o_wslides_course_nav_search ml-1 position-relative">
@@ -52,21 +56,25 @@
<div class="dropdown-menu">
<a class="dropdown-item" href="/slides">Home</a>
<t t-set="dropdown_class" t-value="'dropdown-item %s' % ('active' if not slide else '')"/>
<a t-att-class="'dropdown-item %s' % ('active' if not search_category and not search_tag and not search_slide_type else '')" t-att-href="'/slides/%s' % slug(channel)">
&#9492;<span class="ml-1" t-esc="channel.name"/>
</a>
<a class="dropdown-item active" t-att-aria-current="'page' and search_category" t-if="search_category" t-att-href="'/slides/%s/category/%s' % (slug(channel), slug(search_category))">
<a t-att-class="dropdown_class" t-att-aria-current="'page' and search_category" t-if="search_category" t-att-href="'/slides/%s/category/%s' % (slug(channel), slug(search_category))">
&#9492;<span class="ml-1" t-esc="search_category.name"/>
</a>
<a class="dropdown-item active" t-att-aria-current="'page' and search_tag" t-if="search_tag" t-att-href="'/slides/%s/tag/%s' % (slug(channel), slug(search_tag))">
<a t-att-class="dropdown_class" t-att-aria-current="'page' and search_tag" t-if="search_tag" t-att-href="'/slides/%s/tag/%s' % (slug(channel), slug(search_tag))">
&#9492;<span class="ml-1" t-esc="search_tag.name"/>
</a>
<a class="dropdown-item active" t-att-aria-current="'page' and search_uncategorized" t-if="search_uncategorized" t-att-href="'/slides/%s?search_uncategorized=1' % (slug(channel))">
<a t-att-class="dropdown_class" t-att-aria-current="'page' and search_uncategorized" t-if="search_uncategorized" t-att-href="'/slides/%s?search_uncategorized=1' % (slug(channel))">
&#9492;<span class="ml-1">Uncategorized</span>
</a>
<a class="dropdown-item active" t-att-aria-current="'page' and search_slide_type" t-if="search_slide_type" t-att-href="'/slides/%s?slide_type=%s' % (slug(channel), search_slide_type)">
<a t-att-class="dropdown_class" t-att-aria-current="'page' and search_slide_type" t-if="search_slide_type" t-att-href="'/slides/%s?slide_type=%s' % (slug(channel), search_slide_type)">
&#9492;<span class="ml-1" t-esc="slide_types[search_slide_type]"/>
</a>
<a t-if="slide" class="dropdown-item active" t-att-href="'/slides/slide/%s' % (slug(slide))">
&#9492;<span class="ml-1" t-esc="slide.name"/>
</a>
</div>
</div>
@@ -519,7 +527,7 @@
<t t-set="image_url" t-value="'/web/image/slide.slide/%s/image' % slide_promoted.id"/>
<a t-if="slide_promoted.is_preview or channel.is_member or is_slides_publisher"
t-attf-href="/slides/slide/#{slug(slide_promoted)}" class="w-100 w-lg-50 flex-shrink-0 rounded">
t-attf-href="/slides/slide/#{slug(slide_promoted)}#{query_string}" class="w-100 w-lg-50 flex-shrink-0 rounded">
<div t-attf-style="background-image:url(#{image_url}); background-size: cover; background-position:center; padding-bottom: 35%;" class="h-lg-100"/>
</a>
<div t-else="" class="w-100 w-lg-50 flex-shrink-0 rounded">
@@ -528,7 +536,7 @@
<div class="card-body">
<a t-if="slide_promoted.is_preview or channel.is_member or is_slides_publisher"
t-attf-href="/slides/slide/#{slug(slide_promoted)}"
t-attf-href="/slides/slide/#{slug(slide_promoted)}#{query_string}"
class="h4 d-block" t-att-title="slide_promoted.name" t-field="slide_promoted.name"/>
<h4 t-else="" class="text-muted" t-field="slide_promoted.name"/>
@@ -597,20 +605,20 @@
<t t-if="slide.image">
<t t-set="lesson_image" t-value="'/web/image/slide.slide/%s/image' % slide.id"/>
<a t-if="can_access" t-attf-href="/slides/slide/#{slug(slide)}" t-title="slide.name">
<a t-if="can_access" t-attf-href="/slides/slide/#{slug(slide)}#{query_string}" t-title="slide.name">
<div class="card-img-top border-bottom" t-attf-style="padding-top: 50%; background-image: url(#{lesson_image}); background-size: cover; background-position:center"/>
</a>
<div t-else="" class="card-img-top border-bottom" t-attf-style="padding-top: 50%; background-image: url(#{lesson_image}); background-size: cover; background-position:center"/>
</t>
<t t-else="">
<a t-if="can_access" t-attf-href="/slides/slide/#{slug(slide)}" t-title="slide.name">
<a t-if="can_access" t-attf-href="/slides/slide/#{slug(slide)}#{query_string}" t-title="slide.name">
<div class="card-img-top border-bottom o_wslides_gradient" t-attf-style="padding-top: 50%;"/>
</a>
<div t-else="" class="card-img-top border-bottom o_wslides_gradient" t-attf-style="padding-top: 50%;"/>
</t>
<div class="card-body p-3">
<a t-if="can_access" class="card-title h5 mb-2o_wslides_desc_truncate_2" t-attf-href="/slides/slide/#{slug(slide)}" t-esc="slide.name"/>
<a t-if="can_access" class="card-title h5 mb-2o_wslides_desc_truncate_2" t-attf-href="/slides/slide/#{slug(slide)}#{query_string}" t-esc="slide.name"/>
<span t-else="" class="card-title h5 mb-2 o_wslides_desc_truncate_2 text-muted" t-esc="slide.name"/>
<div class="card-subtitle mb-2 text-muted" t-if="slide.is_preview or (not slide.is_published and user.has_group('website.group_website_publisher'))">
<t t-if="slide.is_preview">
@@ -624,7 +632,7 @@
<div class="o_wslides_desc_truncate_3 font-weight-light oe_no_empty" t-field="slide.description"/>
<div t-if="slide.tag_ids" class="mt-2 pt-1 o_wslides_desc_truncate_2">
<t t-foreach="slide.tag_ids" t-as="tag">
<a t-att-href="'/slides/all?channel_tag_id=%s' % tag.id" class="badge badge-light" t-esc="tag.name"/>
<a t-att-href="'/slides/%s/tag/%s' % (slug(slide.channel_id), slug(tag))" class="badge badge-light" t-esc="tag.name"/>
</t>
</div>
</div>
@@ -178,7 +178,7 @@
<template id='courses_all' name="Odoo All Courses">
<t t-set="body_classname" t-value="'o_wslides_body'"/>
<t t-call="website.layout">
<div class="wrap">
<div id="wrap" class="wrap o_wslides_wrap">
<section class="s_banner bg-900" style="background-image: url(&quot;/website_slides/static/src/img/banner_default_all.svg&quot;); background-size: cover; background-position: 80% 20%">
<div class="container py-5">
<h1 t-if="search_my" class="display-3 mb-0">My Courses</h1>
@@ -185,7 +185,7 @@
<div class="row align-items-center my-3">
<div class="col-12 col-md order-2 order-md-1 d-flex">
<div class="d-flex align-items-center">
<h1 class="h5 my-0">
<h1 class="h4 my-0">
<t t-call="website_slides.slide_icon">
<t t-set="icon_class">mr-1</t>
</t>
@@ -245,36 +245,7 @@
<div t-field="slide.html_content"/>
</div>
</div>
<div class="row" t-if="slide.question_ids">
<t t-call="website_slides.lesson_content_quiz"/>
</div>
<div class="row mt-3 mb-3">
<div class="col-12 col-md d-flex align-items-start mb-4 mb-md-0" t-if="len(slide.link_ids)">
<span t-if="slide.link_ids" class="text-muted font-weight-bold mr-3">External sources</span>
<div class="text-muted mr-auto border-left pl-3">
<t t-foreach="slide.link_ids" t-as="link">
<a t-att-href="link.link" t-esc="link.name"/><br />
</t>
</div>
</div>
<div t-if="slide.channel_id.allow_comment and slide.channel_id.channel_type == 'documentation'"
class="col-12 col-md d-flex align-items-start justify-content-md-end mb-2 mb-md-0">
<span class="text-muted font-weight-bold mr-3">Rating</span>
<div class="text-muted border-left pl-3">
<div class="o_wslides_js_slide_like mr-2">
<span t-att-class="('o_wslides_js_slide_like_up %s') % ('disabled' if not slide.channel_id.can_vote else '')" tabindex="0" data-toggle="popover" t-att-data-slide-id="slide.id">
<i class="fa fa-thumbs-up fa-1x" role="img" aria-label="Likes" title="Likes"></i>
<span t-esc="slide.likes"/>
</span>
<span t-att-class="('o_wslides_js_slide_like_down ml-3 %s') % ('disabled' if not slide.channel_id.can_vote else '')" tabindex="0" data-toggle="popover" t-att-data-slide-id="slide.id">
<i class="fa fa-thumbs-down fa-1x" role="img" aria-label="Dislikes" title="Dislikes"></i>
<span t-esc="slide.dislikes"/>
</span>
</div>
</div>
</div>
</div>
<div class="mb-5">
<div class="my-3">
<ul class="nav nav-tabs o_wslides_lesson_nav" role="tablist">
<li class="nav-item">
<a href="#about" aria-controls="about" class="nav-link active" role="tab" data-toggle="tab">
@@ -362,27 +333,6 @@
</tbody>
</table>
</div>
<div class="col-12 col-md">
<table class="table table-sm">
<tbody>
<tr>
<th colspan="2" class="border-top-0">Share count</th>
</tr>
<tr class="border-top-0">
<th class="border-top-0"><span id="facebook-badge">0</span></th>
<td class="border-top-0 w-100">Facebook</td>
</tr>
<tr>
<th><span id="twitter-badge">0</span></th>
<td>Twitter</td>
</tr>
<tr>
<th><span id="linkedin-badge"/></th>
<td>LinkedIn</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane fade" t-if="slide.website_published" id="share">
@@ -409,6 +359,35 @@
</div>
</div>
</div>
<div class="row" t-if="slide.question_ids">
<t t-call="website_slides.lesson_content_quiz"/>
</div>
<div class="row mt-3 mb-3">
<div class="col-12 col-md d-flex align-items-start mb-4 mb-md-0" t-if="len(slide.link_ids)">
<span t-if="slide.link_ids" class="text-muted font-weight-bold mr-3">External sources</span>
<div class="text-muted mr-auto border-left pl-3">
<t t-foreach="slide.link_ids" t-as="link">
<a t-att-href="link.link" t-esc="link.name"/><br />
</t>
</div>
</div>
<div t-if="slide.channel_id.allow_comment and slide.channel_id.channel_type == 'documentation'"
class="col-12 col-md d-flex align-items-start justify-content-md-end mb-2 mb-md-0">
<span class="text-muted font-weight-bold mr-3">Rating</span>
<div class="text-muted border-left pl-3">
<div class="o_wslides_js_slide_like mr-2">
<span t-att-class="('o_wslides_js_slide_like_up %s') % ('disabled' if not slide.channel_id.can_vote else '')" tabindex="0" data-toggle="popover" t-att-data-slide-id="slide.id">
<i class="fa fa-thumbs-up fa-1x" role="img" aria-label="Likes" title="Likes"></i>
<span t-esc="slide.likes"/>
</span>
<span t-att-class="('o_wslides_js_slide_like_down ml-3 %s') % ('disabled' if not slide.channel_id.can_vote else '')" tabindex="0" data-toggle="popover" t-att-data-slide-id="slide.id">
<i class="fa fa-thumbs-down fa-1x" role="img" aria-label="Dislikes" title="Dislikes"></i>
<span t-esc="slide.dislikes"/>
</span>
</div>
</div>
</div>
</div>
</template>
<!-- Slide sub-tempalte: render a quiz serverside. Should be sync with JS qweb template "slide.slide.quiz" -->
@@ -427,7 +406,7 @@
t-att-data-has-next="1 if next_slide else 0"
t-att-data-next-slide-url="'/slides/slide/%s' % (slug(next_slide)) if next_slide else None">
<div t-foreach="slide_questions" t-as="question"
t-att-class="'o_wslides_js_lesson_quiz_question mt-3 mb-4 %s' % ('completed-disabled' if slide_completed else ('disabled' if not slide.channel_id.is_member else ''))" t-att-data-question-id="question['id']" t-att-data-title="question['question']">
t-att-class="'o_wslides_js_lesson_quiz_question my-5 %s' % ('completed-disabled' if slide_completed else ('disabled' if not slide.channel_id.is_member else ''))" t-att-data-question-id="question['id']" t-att-data-title="question['question']">
<div class="h4">
<small class="text-muted"><span t-esc="question_index+1"/>. </small> <span t-esc="question['question']"/>
</div>
@@ -3,15 +3,18 @@
<!-- Slide template for the fullscreen mode -->
<template id="slide_fullscreen" name="Fullscreen">
<t t-set="head">
<link rel="canonical" t-att-href="slide.website_url" />
</t>
<t t-call="website.layout">
<div class="o_wslides_fs_main d-flex flex-column font-weight-light">
<div class="o_wslides_slide_fs_header d-flex flex-shrink-0 text-white">
<div class="d-flex">
<a class="o_wslides_fs_toggle_sidebar d-flex align-items-center px-3 active" href="#" title="Lessons">
<a class="o_wslides_fs_toggle_sidebar d-flex align-items-center px-3" href="#" title="Lessons">
<i class="fa fa-bars"/><span class="d-none d-md-inline-block ml-1">Lessons</span>
</a>
<a class="o_wslides_fs_review d-flex align-items-center px-3" t-att-href="slide.channel_id.website_url" title="Reviews" t-if="slide.channel_id.allow_comment">
<a class="o_wslides_fs_review d-flex align-items-center px-3" t-att-href="slide.channel_id.website_url + '?active_tab=review'" title="Reviews" t-if="slide.channel_id.allow_comment">
<i class="fa fa-pencil"/><span class="d-none d-md-inline-block ml-1">Write a review</span>
</a>
</div>
@@ -20,7 +23,7 @@
<i class="fa fa-sign-out d-md-none"/><span class="d-none d-md-inline-block ml-1">Exit Fullscreen</span>
</a>
<a class="d-flex align-items-center px-3" t-attf-href="/slides/#{slug(slide.channel_id)}">
<i class="fa fa-sign-out d-md-none"/><span class="d-none d-md-inline-block ml-1">Back to course</span>
<i class="fa fa-home d-md-none"/><span class="d-none d-md-inline-block ml-1">Back to course</span>
</a>
</div>
</div>
@@ -28,7 +31,7 @@
<div class="o_wslides_fs_container d-flex position-relative overflow-hidden flex-grow-1">
<div class="o_wslides_fs_content align-items-stretch justify-content-center d-flex flex-grow-1 order-2"></div>
<div class="o_wslides_fs_sidebar text-white flex-shrink-0 order-1">
<div class="o_wslides_fs_sidebar o_wslides_fs_sidebar_hidden text-white flex-shrink-0 order-1">
<div class="o_wslides_fs_sidebar_content d-flex flex-column px-3 pt-3 h-100">
<div class="o_wslides_fs_sidebar_header mb-3">
<a class="h5 d-block mb-1" t-attf-href="/slides/#{slug(slide.channel_id)}">
@@ -51,12 +51,6 @@
<t t-foreach="course.channel_id.tag_ids" t-as="tag">
<a t-att-href="'/slides/all?channel_tag_id=%s' % tag.id" class="badge badge-light" t-esc="tag.name"/>
</t>
<t t-foreach="course.channel_id.tag_ids" t-as="tag">
<a t-att-href="'/slides/all?channel_tag_id=%s' % tag.id" class="badge badge-light" t-esc="tag.name"/>
</t>
<t t-foreach="course.channel_id.tag_ids" t-as="tag">
<a t-att-href="'/slides/all?channel_tag_id=%s' % tag.id" class="badge badge-light" t-esc="tag.name"/>
</t>
</div>
<div class="d-flex align-items-center">
+15
View File
@@ -0,0 +1,15 @@
Mongolia, 2019-06-14
BumanIT agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Baskhuu Lodoikhuu baskhuujacara@gmail.com https://github.com/jacara
List of contributors:
Baskhuu Lodoikhuu baskhuujacara@gmail.com https://github.com/jacara
+11
View File
@@ -0,0 +1,11 @@
Czech Republic, 2019-06-03
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,
Jakub Hrubý jakub@jhruby.cz https://github.com/jakubhruby
@@ -678,3 +678,33 @@ state_co_30,co,"Guainía","GUA"
state_co_31,co,"Guaviare","GUV"
state_co_32,co,"Vaupés","VAU"
state_co_33,co,"Vichada","VID"
state_mn_01,mn,"Архангай","01"
state_mn_02,mn,"Баян-Өлгий","02"
state_mn_03,mn,"Баянхонгор","03"
state_mn_04,mn,"Булган","04"
state_mn_05,mn,"Говь-Алтай","05"
state_mn_06,mn,"Дорноговь","06"
state_mn_07,mn,"Дорнод","07"
state_mn_08,mn,"Дундговь","08"
state_mn_09,mn,"Завхан","09"
state_mn_10,mn,"Өвөрхангай","10"
state_mn_11,mn,"Өмнөговь","11"
state_mn_12,mn,"Сүхбаатар","12"
state_mn_13,mn,"Сэлэнгэ","13"
state_mn_14,mn,"Төв","14"
state_mn_15,mn,"Увс","15"
state_mn_16,mn,"Ховд","16"
state_mn_17,mn,"Хөвсгөл","17"
state_mn_18,mn,"Хэнтий","18"
state_mn_19,mn,"Дархан-Уул","19"
state_mn_20,mn,"Орхон","20"
state_mn_23,mn,"УБ - Хан Уул","23"
state_mn_24,mn,"УБ - Баянзүрх","24"
state_mn_25,mn,"УБ - Сүхбаатар","25"
state_mn_26,mn,"УБ - Баянгол","26"
state_mn_27,mn,"УБ - Багануур","27"
state_mn_28,mn,"УБ - Багахангай","28"
state_mn_29,mn,"УБ - Налайх","29"
state_mn_32,mn,"Говьсүмбэр","32"
state_mn_34,mn,"УБ - Сонгино Хайрхан","34"
state_mn_35,mn,"УБ - Чингэлтэй","35"
1 id country_id:id name code
678 state_co_31 co Guaviare GUV
679 state_co_32 co Vaupés VAU
680 state_co_33 co Vichada VID
681 state_mn_01 mn Архангай 01
682 state_mn_02 mn Баян-Өлгий 02
683 state_mn_03 mn Баянхонгор 03
684 state_mn_04 mn Булган 04
685 state_mn_05 mn Говь-Алтай 05
686 state_mn_06 mn Дорноговь 06
687 state_mn_07 mn Дорнод 07
688 state_mn_08 mn Дундговь 08
689 state_mn_09 mn Завхан 09
690 state_mn_10 mn Өвөрхангай 10
691 state_mn_11 mn Өмнөговь 11
692 state_mn_12 mn Сүхбаатар 12
693 state_mn_13 mn Сэлэнгэ 13
694 state_mn_14 mn Төв 14
695 state_mn_15 mn Увс 15
696 state_mn_16 mn Ховд 16
697 state_mn_17 mn Хөвсгөл 17
698 state_mn_18 mn Хэнтий 18
699 state_mn_19 mn Дархан-Уул 19
700 state_mn_20 mn Орхон 20
701 state_mn_23 mn УБ - Хан Уул 23
702 state_mn_24 mn УБ - Баянзүрх 24
703 state_mn_25 mn УБ - Сүхбаатар 25
704 state_mn_26 mn УБ - Баянгол 26
705 state_mn_27 mn УБ - Багануур 27
706 state_mn_28 mn УБ - Багахангай 28
707 state_mn_29 mn УБ - Налайх 29
708 state_mn_32 mn Говьсүмбэр 32
709 state_mn_34 mn УБ - Сонгино Хайрхан 34
710 state_mn_35 mn УБ - Чингэлтэй 35
@@ -222,6 +222,7 @@
<field name="symbol">¥</field>
<field name="rounding">1.00</field>
<field name="active" eval="False"/>
<field name="position">before</field>
<field name="currency_unit_label">Yen</field>
<field name="currency_subunit_label">Cen</field>
</record>
+4 -1
View File
@@ -251,7 +251,10 @@ class IrActionsActWindow(models.Model):
existing = self.filtered(lambda rec: rec.id in ids)
if len(existing) < len(self):
# mark missing records in cache with a failed value
exc = MissingError(_("Record does not exist or has been deleted."))
exc = MissingError(
_("Record does not exist or has been deleted.")
+ '\n\n({} {}, {} {})'.format(_('Records:'), (self - existing).ids[:6], _('User:'), self._uid)
)
for record in (self - existing):
record._cache.set_failed(self._fields, exc)
return existing
+5 -3
View File
@@ -353,9 +353,11 @@ class IrHttp(models.AbstractModel):
headers = [('Content-Type', mimetype), ('X-Content-Type-Options', 'nosniff')]
# cache
etag = bool(request) and request.httprequest.headers.get('If-None-Match')
status = status or (304 if filehash and etag == filehash else 200)
status = status or 200
if filehash:
headers.append(('ETag', filehash))
if etag == filehash and status == 200:
status = 304
headers.append(('Cache-Control', 'max-age=%s' % (STATIC_CACHE if unique else 0)))
# content-disposition default name
if download:
@@ -398,11 +400,11 @@ class IrHttp(models.AbstractModel):
status, content, filename, mimetype, filehash = self._binary_ir_attachment_redirect_content(record, default_mimetype=default_mimetype)
if not content:
status, content, filename, mimetype, filehash = self._binary_record_content(
record, field=field, filename=None, filename_field=filename_field,
record, field=field, filename=filename, filename_field=filename_field,
default_mimetype='application/octet-stream')
status, headers, content = self._binary_set_headers(
status, content, filename, mimetype, unique, filehash=False, download=download)
status, content, filename, mimetype, unique, filehash=filehash, download=download)
return status, headers, content
+7 -1
View File
@@ -35,4 +35,10 @@ class IrLogging(models.Model):
@api.model_cr
def init(self):
super(IrLogging, self).init()
self._cr.execute("ALTER TABLE ir_logging DROP CONSTRAINT IF EXISTS ir_logging_write_uid_fkey")
self._cr.execute("select 1 from information_schema.constraint_column_usage where table_name = 'ir_logging' and constraint_name = 'ir_logging_write_uid_fkey'")
if self._cr.rowcount:
# DROP CONSTRAINT unconditionally takes an ACCESS EXCLUSIVE lock
# on the table, even "IF EXISTS" is set and not matching; disabling
# the relevant trigger instead acquires SHARE ROW EXCLUSIVE, which
# still conflicts with the ROW EXCLUSIVE needed for an insert
self._cr.execute("ALTER TABLE ir_logging DROP CONSTRAINT ir_logging_write_uid_fkey")
+1
View File
@@ -1275,6 +1275,7 @@ class IrModelAccess(models.Model):
msg_params['groups_list'] = groups
else:
msg_tail = _("No group currently allows this operation.")
msg_tail += ' - ({} {}, {} {})'.format(_('Operation:'), mode, _('User:'), self._uid)
_logger.info('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, self._uid, model)
msg = '%s %s' % (msg_heads[mode], msg_tail)
raise AccessError(msg % msg_params)
@@ -47,7 +47,7 @@ class TestACLFeedback(Feedback):
self.record.sudo(self.user).write({'val': 10})
self.assertEqual(
ctx.exception.args[0],
"""Sorry, you are not allowed to modify documents of type 'Object For Test Access Right' (test_access_right.some_obj). No group currently allows this operation."""
"""Sorry, you are not allowed to modify documents of type 'Object For Test Access Right' (test_access_right.some_obj). No group currently allows this operation. - (Operation: write, User: %d)""" % self.user.id
)
def test_one_group(self):
@@ -57,12 +57,12 @@ class TestACLFeedback(Feedback):
})
self.assertEqual(
ctx.exception.args[0],
"""Sorry, you are not allowed to create documents of type 'Object For Test Access Right' (test_access_right.some_obj). This operation is allowed for the groups:\n\t- Group 0"""
"""Sorry, you are not allowed to create documents of type 'Object For Test Access Right' (test_access_right.some_obj). This operation is allowed for the groups:\n\t- Group 0 - (Operation: create, User: %d)""" % self.user.id
)
def test_two_groups(self):
r = self.record.sudo(self.user)
expected = """Sorry, you are not allowed to access documents of type 'Object For Test Access Right' (test_access_right.some_obj). This operation is allowed for the groups:\n\t- Group 0\n\t- Group 1"""
expected = """Sorry, you are not allowed to access documents of type 'Object For Test Access Right' (test_access_right.some_obj). This operation is allowed for the groups:\n\t- Group 0\n\t- Group 1 - (Operation: read, User: %d)""" % self.user.id
with self.assertRaises(AccessError) as ctx:
# noinspection PyStatementEffect
r.val
+3 -2
View File
@@ -29,7 +29,7 @@ def log(logger, level, prefix, msg, depth=None):
path_prefix = os.path.realpath(os.path.dirname(os.path.dirname(__file__)))
class PostgreSQLHandler(logging.Handler):
""" PostgreSQL Loggin Handler will store logs in the database, by default
""" PostgreSQL Logging Handler will store logs in the database, by default
the current database, can be set using --log-db=DBNAME
"""
def emit(self, record):
@@ -39,7 +39,8 @@ class PostgreSQLHandler(logging.Handler):
if not dbname:
return
with tools.ignore(Exception), tools.mute_logger('odoo.sql_db'), sql_db.db_connect(dbname, allow_uri=True).cursor() as cr:
cr.autocommit(True)
# preclude risks of deadlocks
cr.execute("SET LOCAL statement_timeout = 1000")
msg = tools.ustr(record.msg)
if record.args:
msg = msg % record.args
+4 -4
View File
@@ -487,7 +487,7 @@ class ChromeBrowser():
self.chrome_process.wait()
if self.user_data_dir and os.path.isdir(self.user_data_dir) and self.user_data_dir != '/':
self._logger.info('Removing chrome user profile "%s"', self.user_data_dir)
shutil.rmtree(self.user_data_dir)
shutil.rmtree(self.user_data_dir, ignore_errors=True)
# Restore previous signal handler
if self.sigxcpu_handler and os.name == 'posix':
signal.signal(signal.SIGXCPU, self.sigxcpu_handler)
@@ -828,12 +828,12 @@ class HttpCase(TransactionCase):
self.opener = requests.Session()
self.opener.cookies['session_id'] = self.session_id
def url_open(self, url, data=None, timeout=10):
def url_open(self, url, data=None, timeout=10, headers=None):
if url.startswith('/'):
url = "http://%s:%s%s" % (HOST, PORT, url)
if data:
return self.opener.post(url, data=data, timeout=timeout)
return self.opener.get(url, timeout=timeout)
return self.opener.post(url, data=data, timeout=timeout, headers=headers)
return self.opener.get(url, timeout=timeout, headers=headers)
def _wait_remaining_requests(self):
t0 = int(time.time())