[FIX] hr_timesheet: make portal view readonly for ts
Currently, there is an access right problem concerning timesheet. The module mrp gives write access to portal user on analytic line, but it is not something we want for the project app. step to reproduce: - install hr_timesheet on a saas-16.3 db - go to project app, open office design - select any task, and add a subtask to it - go the the view form of the subtask - create a new timesheet for this subtask - go back to the view form of the parent task a button 'subtask timesheet' is now visible - share the project in edit mode with a portal user - connect with that portal user - open the office design project - go to the view form of the parent task and click on the button - the user is redirected to a view tree with the timesheet of the subtask this view tree is editable Source of the problem: in the mrp module, the access rights of the analytic line is overwritten to give write access to it to portal user. Soluce: We have to make the view in readonly mode for timesheet when the connected user is a portal one. Unfortunately, it is not 100% possible because setting the field to readonly still gives the user the opportunity to click on those fields (which triggers access errors). So we have to create new views and specifically open those views when the user is a portal one. affected version: saas-16.3 - master task - 3751315 closes odoo/odoo#160959 X-original-commit: 7b733f118f60ff98d9f82c69453187d70e6bece0 Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
This commit is contained in:
@@ -84,9 +84,14 @@ class AccountAnalyticLine(models.Model):
|
||||
return False
|
||||
|
||||
def _compute_readonly_timesheet(self):
|
||||
readonly_timesheets = self.filtered(lambda timesheet: timesheet._is_readonly())
|
||||
readonly_timesheets.readonly_timesheet = True
|
||||
(self - readonly_timesheets).readonly_timesheet = False
|
||||
# Since the mrp_module gives write access to portal user on timesheet, we check that the user is an internal one before giving the write access.
|
||||
# It is not supposed to be needed, since portal user are not supposed to have access to the views using this field, but better be safe than sorry
|
||||
if not self.env.user.has_group('base.group_user'):
|
||||
self.readonly_timesheet = True
|
||||
else:
|
||||
readonly_timesheets = self.filtered(lambda timesheet: timesheet._is_readonly())
|
||||
readonly_timesheets.readonly_timesheet = True
|
||||
(self - readonly_timesheets).readonly_timesheet = False
|
||||
|
||||
def _compute_encoding_uom_id(self):
|
||||
for analytic_line in self:
|
||||
|
||||
@@ -161,14 +161,32 @@ class Task(models.Model):
|
||||
|
||||
def action_view_subtask_timesheet(self):
|
||||
self.ensure_one()
|
||||
is_internal_user = self.env.user.has_group('base.group_user')
|
||||
task_ids = self.with_context(active_test=False)._get_subtask_ids_per_task_id().get(self.id, [])
|
||||
action = self.env["ir.actions.actions"]._for_xml_id("hr_timesheet.timesheet_action_all")
|
||||
graph_view_id = self.env.ref("hr_timesheet.view_hr_timesheet_line_graph_by_employee").id
|
||||
new_views = []
|
||||
for view in action['views']:
|
||||
if not is_internal_user:
|
||||
if view[1] == 'tree':
|
||||
tree_view_id = self.env['ir.model.data']._xmlid_to_res_id('hr_timesheet.hr_timesheet_line_portal_tree')
|
||||
if tree_view_id:
|
||||
new_views.insert(0, (tree_view_id, 'tree'))
|
||||
continue
|
||||
elif view[1] == 'form':
|
||||
form_view_id = self.env['ir.model.data']._xmlid_to_res_id('hr_timesheet.timesheet_view_form_portal_user')
|
||||
if form_view_id:
|
||||
new_views.append((form_view_id, 'form'))
|
||||
continue
|
||||
elif view[1] == 'kanban':
|
||||
kanban_view_id = self.env['ir.model.data']._xmlid_to_res_id('hr_timesheet.view_kanban_account_analytic_line_portal_user')
|
||||
if kanban_view_id:
|
||||
new_views.append((kanban_view_id, 'kanban'))
|
||||
continue
|
||||
if view[1] == 'graph':
|
||||
view = (graph_view_id, 'graph')
|
||||
new_views.insert(0, view) if view[1] == 'tree' else new_views.append(view)
|
||||
|
||||
action.update({
|
||||
'display_name': _('Timesheets'),
|
||||
'context': {'default_project_id': self.project_id.id, 'grid_range': 'week'},
|
||||
|
||||
@@ -23,3 +23,48 @@ class TestPortalTimesheet(TestProjectSharingCommon):
|
||||
self.env.invalidate_all()
|
||||
# Should not raise any access error
|
||||
self.env['account.analytic.line'].with_user(self.user_portal).get_view(view_type=view)
|
||||
|
||||
def test_action_view_subtask_timesheet(self):
|
||||
""" Ensure that the action view_subtask_timesheet is accessible without
|
||||
raising an error for all portal users
|
||||
"""
|
||||
# A portal collaborator is added to a project to enable the rule analytic.account.analytic.line.timesheet.portal.user
|
||||
self.project_portal.write({
|
||||
'collaborator_ids': [
|
||||
Command.create({'partner_id': self.user_portal.partner_id.id}),
|
||||
],
|
||||
})
|
||||
action = self.task_portal.action_view_subtask_timesheet()
|
||||
tree_view_id = form_view_id = kanban_view_id = False
|
||||
for view_id, view_type in action['views']:
|
||||
if view_type == 'tree':
|
||||
tree_view_id = view_id
|
||||
elif view_type == 'form':
|
||||
form_view_id = view_id
|
||||
elif view_type == 'kanban':
|
||||
kanban_view_id = view_id
|
||||
|
||||
action = self.task_portal.with_user(self.user_portal).action_view_subtask_timesheet()
|
||||
portal_tree_view_id = self.env['ir.model.data']._xmlid_to_res_id('hr_timesheet.hr_timesheet_line_portal_tree')
|
||||
portal_form_view_id = self.env['ir.model.data']._xmlid_to_res_id('hr_timesheet.timesheet_view_form_portal_user')
|
||||
portal_kanban_view_id = self.env['ir.model.data']._xmlid_to_res_id('hr_timesheet.view_kanban_account_analytic_line_portal_user')
|
||||
if portal_tree_view_id and portal_form_view_id and portal_kanban_view_id:
|
||||
# no need to check that if the views are not installed or already removed
|
||||
for view_id, view_type in action['views']:
|
||||
if view_type == 'tree':
|
||||
self.assertEqual(view_id, portal_tree_view_id)
|
||||
elif view_type == 'form':
|
||||
self.assertEqual(view_id, portal_form_view_id)
|
||||
elif view_type == 'kanban':
|
||||
self.assertEqual(view_id, portal_kanban_view_id)
|
||||
|
||||
self.env['ir.ui.view'].browse([portal_tree_view_id, portal_form_view_id, portal_kanban_view_id]).unlink()
|
||||
|
||||
action = self.task_portal.with_user(self.user_portal).action_view_subtask_timesheet()
|
||||
for view_id, view_type in action['views']:
|
||||
if view_type == 'tree':
|
||||
self.assertEqual(view_id, tree_view_id)
|
||||
elif view_type == 'form':
|
||||
self.assertEqual(view_id, form_view_id)
|
||||
elif view_type == 'kanban':
|
||||
self.assertEqual(view_id, kanban_view_id)
|
||||
|
||||
@@ -23,6 +23,24 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_timesheet_line_portal_tree" model="ir.ui.view">
|
||||
<field name="name">portal.hr_timesheet.account.analytic.line.tree</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet_line_tree"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//tree" position="attributes">
|
||||
<attribute name="edit">0</attribute>
|
||||
<attribute name="create">0</attribute>
|
||||
<attribute name="delete">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='task_id']" position="attributes">
|
||||
<attribute name="options">{'no_create_edit': True, 'no_open': True}</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_view_tree_user" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.view.tree.with.user</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
@@ -194,6 +212,31 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_view_form_portal_user" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.form</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.timesheet_view_form_user"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='employee_id']" position="attributes">
|
||||
<attribute name="required">1</attribute>
|
||||
<attribute name="widget">many2one_avatar_employee</attribute>
|
||||
<attribute name="context">{'active_test': True}</attribute>
|
||||
<attribute name="options">{'no_open': True}</attribute>
|
||||
<attribute name="readonly">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='project_id']" position="attributes">
|
||||
<attribute name="options">{'no_create_edit': True, 'no_open': True}</attribute>
|
||||
<attribute name="readonly">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='task_id']" position="attributes">
|
||||
<attribute name="options">{'no_create_edit': True, 'no_open': True}</attribute>
|
||||
<attribute name="readonly">1</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_timesheet_line_my_timesheet_search" model="ir.ui.view">
|
||||
<field name="name">view.search.my.timesheet.menu</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
@@ -261,6 +304,19 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_kanban_account_analytic_line_portal_user" model="ir.ui.view">
|
||||
<field name="name">portal.account.analytic.line.kanban</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.view_kanban_account_analytic_line"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//img[hasclass('o_image_40_cover')]" position="replace"/>
|
||||
<xpath expr="//div[hasclass('o_kanban_record_headings')]" position="attributes">
|
||||
<attribute name="class">o_kanban_record_headings pe-2</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<!--
|
||||
Actions
|
||||
-->
|
||||
|
||||
Reference in New Issue
Block a user