diff --git a/addons/portal/views/portal_views.xml b/addons/portal/views/portal_views.xml
index a48cc2c68f6..629e0cfc7d9 100644
--- a/addons/portal/views/portal_views.xml
+++ b/addons/portal/views/portal_views.xml
@@ -1,19 +1,5 @@
-
-
-
-
-
-
-
diff --git a/addons/portal_sale/views/sale_order_views.xml b/addons/portal_sale/views/sale_order_views.xml
index a2230aa2264..2075deb64c9 100644
--- a/addons/portal_sale/views/sale_order_views.xml
+++ b/addons/portal_sale/views/sale_order_views.xml
@@ -12,120 +12,4 @@
-
- sale.order.form.portal_sale
- sale.order
-
-
-
-
-
-
-
-
-
-
-
-
-
- {}
-
-
- {}
-
-
-
-
-
-
-
- {}
-
-
- {}
-
-
-
-
-
-
-
-
-
-
-
-
- account.invoice.form
- account.invoice
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Quotations
- ir.actions.act_window
- sale.order
- tree,form
- [('state','in',('draft','sent','cancel'))]
- {}
-
- We haven't sent you any quotation.
-
-
-
- Sale Orders
- ir.actions.act_window
- sale.order
- tree,form
-
- [('state', 'not in', ('draft', 'sent', 'cancel'))]
- {}
- We haven't sent you any sales order.
-
-
-
- Invoices
- account.invoice
- tree,form
- [('type','in',['out_invoice','out_refund'])]
- {}
-
- We haven't sent you any invoice.
-
-
-
-
-
- tree
-
-
-
-
-
-
- form
-
-
-
-
-
-
-
diff --git a/addons/project/__openerp__.py b/addons/project/__openerp__.py
index 2f44c5ef01d..23614d67313 100644
--- a/addons/project/__openerp__.py
+++ b/addons/project/__openerp__.py
@@ -13,7 +13,6 @@
'product',
'analytic',
'mail',
- 'portal',
'resource',
'web_kanban',
'web_planner',
diff --git a/addons/project/data/project_demo.xml b/addons/project/data/project_demo.xml
index 95ba9da9078..f2201f6fed5 100644
--- a/addons/project/data/project_demo.xml
+++ b/addons/project/data/project_demo.xml
@@ -65,14 +65,10 @@
Website for Sales & WMS
3
-
- portal
project.task
-
Research & Development
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index a979fd74317..a2a17937ff4 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -104,14 +104,6 @@ class Project(models.Model):
""" Overriden in project_issue to offer more options """
return [('project.task', "Tasks")]
- def _get_visibility_selection(self):
- """ Overriden in portal_project to offer more options """
- return [
- ('employees', _('Visible by all employees')),
- ('followers', _('On invitation only')),
- ('portal', _('Shared with a customer'))
- ]
-
@api.multi
def attachment_tree_view(self):
self.ensure_one()
@@ -171,7 +163,6 @@ class Project(models.Model):
# Lambda indirection method to avoid passing a copy of the overridable method when declaring the field
_alias_models = lambda self: self._get_alias_models()
- _visibility_selection = lambda self: self._get_visibility_selection()
active = fields.Boolean(default=True,
help="If the active field is set to False, it will allow you to hide the project without removing it.")
@@ -203,15 +194,19 @@ class Project(models.Model):
"with Tasks (or optionally Issues if the Issue Tracker module is installed).")
alias_model = fields.Selection(_alias_models, string="Alias Model", index=True, required=True, default='project.task',
help="The kind of document created when an email is received on this project's email alias")
- privacy_visibility = fields.Selection(_visibility_selection, string='Privacy', required=True,
+ privacy_visibility = fields.Selection([
+ ('followers', _('On invitation only')),
+ ('employees', _('Visible by all employees')),
+ ('portal', _('Visible by following customers')),
+ ],
+ string='Privacy', required=True,
default='employees',
help="Holds visibility of the tasks or issues that belong to the current project:\n"
- "- Portal : employees see everything;\n"
- " if portal is activated, portal users see the tasks or issues followed by\n"
- " them or by someone of their company\n"
- "- Employees Only: employees see all tasks or issues\n"
- "- Followers Only: employees see only the followed tasks or issues; if portal\n"
- " is activated, portal users see the followed tasks or issues.")
+ "- On invitation only: Employees may only see the followed project, tasks or issues\n"
+ "- Visible by all employees: Employees may see all project, tasks or issues\n"
+ "- Visible by following customers: employees see everything;\n"
+ " if website is activated, portal users may see project, tasks or issues followed by\n"
+ " them or by someone of their company\n")
doc_count = fields.Integer(compute='_compute_attached_docs_count', string="Number of documents attached")
date_start = fields.Date(string='Start Date')
date = fields.Date(string='Expiration Date', index=True, track_visibility='onchange')
diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv
index 5e6a9c682e6..6825917202e 100644
--- a/addons/project/security/ir.model.access.csv
+++ b/addons/project/security/ir.model.access.csv
@@ -3,7 +3,6 @@ access_project_project,project.project,model_project_project,project.group_proje
access_project_project_manager,project.project,model_project_project,project.group_project_manager,1,1,1,1
access_account_analytic_account_user,account.analytic.account,analytic.model_account_analytic_account,project.group_project_user,1,0,0,0
access_account_analytic_account_manager,account.analytic.account,analytic.model_account_analytic_account,project.group_project_manager,1,1,1,1
-access_account_analytic_account_portal,account_analytic_account,analytic.model_account_analytic_account,base.group_portal,1,0,0,0
access_project_task_type_user,project.task.type.user,model_project_task_type,base.group_user,1,0,0,0
access_project_task_type_project_user,project.task.type.project.user,model_project_task_type,project.group_project_user,1,0,0,0
access_project_task_type_manager,project.task.type manager,model_project_task_type,project.group_project_manager,1,1,1,1
@@ -21,8 +20,4 @@ access_resource_calendar_attendance,project.resource_calendar_attendance user,re
access_resource_calendar_leaves_user,resource.calendar.leaves user,resource.model_resource_calendar_leaves,project.group_project_user,1,1,1,1
access_project_tags_all,project.project_tags_all,model_project_tags,,1,0,0,0
access_project_tags_manager,project.project_tags_manager,model_project_tags,project.group_project_manager,1,1,1,1
-access_project_tags_portal,project_tags_portal,project.model_project_tags,base.group_portal,1,0,0,0
access_mail_alias,mail.alias,mail.model_mail_alias,project.group_project_manager,1,1,1,1
-access_project_portal,project__portal,project.model_project_project,base.group_portal,1,0,0,0
-access_task_portal,task_portal,project.model_project_task,base.group_portal,1,0,0,0
-access_task_type_portal,task_type_portal,project.model_project_task_type,base.group_portal,1,0,0,0
diff --git a/addons/project/security/project_security.xml b/addons/project/security/project_security.xml
index 56c51d7eea2..7f06484836c 100644
--- a/addons/project/security/project_security.xml
+++ b/addons/project/security/project_security.xml
@@ -43,10 +43,10 @@
- Project: employees: portal, employees or followers
+ Project: employees: following required for follower-only projects
['|',
- ('privacy_visibility', 'in', ['portal', 'employees']),
+ ('privacy_visibility', '!=', 'followers'),
('message_partner_ids', 'in', [user.partner_id.id])
]
@@ -63,15 +63,13 @@
- Project/Task: employees: portal or employee or (followers and following)
+ Project/Task: employees: follow required for follower-only projects
[
'|',
- ('project_id.privacy_visibility', 'in', ['portal', 'employees']),
+ ('project_id.privacy_visibility', '!=', 'followers'),
'|',
- '&',
- ('project_id.privacy_visibility', '=', 'followers'),
- ('project_id.message_partner_ids', 'in', [user.partner_id.id]),
+ ('project_id.message_partner_ids', 'in', [user.partner_id.id]),
'|',
('message_partner_ids', 'in', [user.partner_id.id]),
# to subscribe check access to the record, follower is not enough at creation
@@ -87,42 +85,6 @@
-
-
- Project: portal users: portal or following
-
- [
- '|',
- '&',
- ('privacy_visibility', '=', 'portal'),
- ('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
- '&',
- ('privacy_visibility', '=', 'followers'),
- ('message_partner_ids', 'in', [user.partner_id.id])
- ]
-
-
-
-
- Project/Task: portal users: (portal and colleagues following) or (followers and following)
-
- [
- '|',
- '|',
- '&',
- ('project_id.privacy_visibility', '=', 'portal'),
- ('project_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
- '&',
- ('project_id.privacy_visibility', '=', 'followers'),
- ('project_id.message_partner_ids', 'in', [user.partner_id.id]),
- '&',
- # on employee project can receive messages but not access the object
- ('project_id.privacy_visibility', '!=', 'employees'),
- ('message_partner_ids', 'in', [user.partner_id.id])
- ]
-
-
-
Task Analysis multi-company
diff --git a/addons/project/tests/test_access_rights.py b/addons/project/tests/test_access_rights.py
index 4add7a186c3..db77ecc92cb 100644
--- a/addons/project/tests/test_access_rights.py
+++ b/addons/project/tests/test_access_rights.py
@@ -31,37 +31,6 @@ class TestPortalProjectBase(TestProjectBase):
class TestPortalProject(TestPortalProjectBase):
- @mute_logger('openerp.addons.base.ir.ir_model')
- def test_portal_project_access_rights(self):
- pigs = self.project_pigs
- pigs.write({'privacy_visibility': 'portal'})
-
- # Do: Alfred reads project -> ok (employee ok public)
- pigs.sudo(self.user_projectuser).read(['user_id'])
- # Test: all project tasks visible
- tasks = self.env['project.task'].sudo(self.user_projectuser).search([('project_id', '=', pigs.id)])
- self.assertEqual(tasks, self.task_1 | self.task_2 | self.task_3 | self.task_4 | self.task_5 | self.task_6,
- 'access rights: project user should see all tasks of a portal project')
-
- # Do: Bert reads project -> crash, no group
- self.assertRaises(AccessError, pigs.sudo(self.user_noone).read, ['user_id'])
- # Test: no project task searchable
- self.assertRaises(AccessError, self.env['project.task'].sudo(self.user_noone).search, [('project_id', '=', pigs.id)])
-
- # Data: task follower
- pigs.sudo(self.user_projectmanager).message_subscribe_users(user_ids=[self.user_portal.id])
- self.task_1.sudo(self.user_projectuser).message_subscribe_users(user_ids=[self.user_portal.id])
- self.task_3.sudo(self.user_projectuser).message_subscribe_users(user_ids=[self.user_portal.id])
- # Do: Chell reads project -> ok (portal ok public)
- pigs.sudo(self.user_portal).read(['user_id'])
- # Do: Donovan reads project -> ko (public ko portal)
- self.assertRaises(AccessError, pigs.sudo(self.user_public).read, ['user_id'])
- # Test: no access right to project.task
- self.assertRaises(AccessError, self.env['project.task'].sudo(self.user_public).search, [])
- # Data: task follower cleaning
- self.task_1.sudo(self.user_projectuser).message_unsubscribe_users(user_ids=[self.user_portal.id])
- self.task_3.sudo(self.user_projectuser).message_unsubscribe_users(user_ids=[self.user_portal.id])
-
@mute_logger('openerp.addons.base.ir.ir_model')
def test_employee_project_access_rights(self):
pigs = self.project_pigs
@@ -76,11 +45,6 @@ class TestPortalProject(TestPortalProjectBase):
'access rights: project user cannot see all tasks of an employees project')
# Do: Bert reads project -> crash, no group
self.assertRaises(AccessError, pigs.sudo(self.user_noone).read, ['user_id'])
- # Do: Chell reads project -> ko (portal ko employee)
- self.assertRaises(AccessError, pigs.sudo(self.user_portal).read, ['user_id'])
- # Test: no project task visible + assigned
- tasks = self.env['project.task'].sudo(self.user_portal).search([('project_id', '=', pigs.id)])
- self.assertFalse(tasks.ids, 'access rights: portal user should not see tasks of an employees project, even if assigned')
# Do: Donovan reads project -> ko (public ko employee)
self.assertRaises(AccessError, pigs.sudo(self.user_public).read, ['user_id'])
# Do: project user is employee and can create a task
@@ -104,27 +68,16 @@ class TestPortalProject(TestPortalProjectBase):
# Do: Bert reads project -> crash, no group
self.assertRaises(AccessError, pigs.sudo(self.user_noone).read, ['user_id'])
- # Do: Chell reads project -> ko (portal ko employee)
- self.assertRaises(AccessError, pigs.sudo(self.user_portal).read, ['user_id'])
- # Test: no project task visible
- tasks = self.env['project.task'].sudo(self.user_portal).search([('project_id', '=', pigs.id)])
- self.assertEqual(tasks, self.task_3,
- 'access rights: portal user should not see tasks of a not-followed followers project, only assigned')
-
# Do: Donovan reads project -> ko (public ko employee)
self.assertRaises(AccessError, pigs.sudo(self.user_public).read, ['user_id'])
- # Data: subscribe Alfred, Chell and Donovan as follower
- pigs.message_subscribe_users(user_ids=[self.user_projectuser.id, self.user_portal.id, self.user_public.id])
- self.task_1.sudo(self.user_projectmanager).message_subscribe_users(user_ids=[self.user_portal.id, self.user_projectuser.id])
- self.task_3.sudo(self.user_projectmanager).message_subscribe_users(user_ids=[self.user_portal.id, self.user_projectuser.id])
+ pigs.message_subscribe_users(user_ids=[self.user_projectuser.id])
# Do: Alfred reads project -> ok (follower ok followers)
prout = pigs.sudo(self.user_projectuser)
prout.invalidate_cache()
prout.read(['user_id'])
- # Do: Chell reads project -> ok (follower ok follower)
- pigs.sudo(self.user_portal).read(['user_id'])
+
# Do: Donovan reads project -> ko (public ko follower even if follower)
self.assertRaises(AccessError, pigs.sudo(self.user_public).read, ['user_id'])
# Do: project user is follower of the project and can create a task
diff --git a/addons/project/tests/test_project_base.py b/addons/project/tests/test_project_base.py
index 189fc0cab21..ed354fa4bb5 100644
--- a/addons/project/tests/test_project_base.py
+++ b/addons/project/tests/test_project_base.py
@@ -32,7 +32,7 @@ class TestProjectBase(TestMail):
# Test 'Pigs' project
cls.project_pigs = cls.env['project.project'].with_context({'mail_create_nolog': True}).create({
'name': 'Pigs',
- 'privacy_visibility': 'portal',
+ 'privacy_visibility': 'employees',
'alias_name': 'project+pigs',
'partner_id': cls.partner_1.id})
# Already-existing tasks in Pigs
@@ -48,7 +48,7 @@ class TestProjectBase(TestMail):
# Test 'Goats' project, same as 'Pigs', but with 2 stages
cls.project_goats = cls.env['project.project'].with_context({'mail_create_nolog': True}).create({
'name': 'Goats',
- 'privacy_visibility': 'portal',
+ 'privacy_visibility': 'followers',
'alias_name': 'project+goats',
'partner_id': cls.partner_1.id,
'type_ids': [
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml
index 4937c41d29e..b75ef74a358 100644
--- a/addons/project/views/project_views.xml
+++ b/addons/project/views/project_views.xml
@@ -107,8 +107,7 @@
-
+
@@ -744,10 +743,6 @@
-
-
-
-
-
-
- Issues
- project.issue
- form
- tree,form
-
-
- {"search_default_user_id":'', "search_default_draft":'', "search_default_todo":'', "portal":'True'}
-
- current
-
-
- Click to create an issue.
-
- You can track your issues from this menu and the action we will take.
-
-
-
-
diff --git a/addons/website_portal/__openerp__.py b/addons/website_portal/__openerp__.py
index b3f7fd6800b..20cbfaf99d2 100644
--- a/addons/website_portal/__openerp__.py
+++ b/addons/website_portal/__openerp__.py
@@ -12,5 +12,4 @@ Allows your customers to manage their account from a beautiful web interface.
'data': [
'views/templates.xml',
],
- 'installable': True,
}
diff --git a/addons/website_portal/views/templates.xml b/addons/website_portal/views/templates.xml
index 517a75d29e0..fa4baaf5d68 100644
--- a/addons/website_portal/views/templates.xml
+++ b/addons/website_portal/views/templates.xml
@@ -19,7 +19,7 @@
-
+
@@ -77,7 +77,7 @@
-
+
@@ -197,4 +197,5 @@
My Account
+
diff --git a/addons/website_project/__init__.py b/addons/website_project/__init__.py
new file mode 100644
index 00000000000..bf7fb7b45c1
--- /dev/null
+++ b/addons/website_project/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import controllers
diff --git a/addons/website_project/__openerp__.py b/addons/website_project/__openerp__.py
new file mode 100644
index 00000000000..e105d8177a7
--- /dev/null
+++ b/addons/website_project/__openerp__.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+{
+ 'name': "Website Project",
+ 'description': """
+Website portal for Project and Tasks
+====================================
+ """,
+ 'category': 'Website',
+ 'depends': ['project', 'website_portal'],
+ 'data': [
+ 'security/project_security.xml',
+ 'security/ir.model.access.csv',
+ 'views/project_templates.xml',
+ ],
+ 'demo': [
+ 'demo/project_demo.xml',
+ ],
+ 'auto_install': True,
+}
diff --git a/addons/website_project/controllers/__init__.py b/addons/website_project/controllers/__init__.py
new file mode 100644
index 00000000000..f48716d3a69
--- /dev/null
+++ b/addons/website_project/controllers/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import main
diff --git a/addons/website_project/controllers/main.py b/addons/website_project/controllers/main.py
new file mode 100644
index 00000000000..d4ecfff31b8
--- /dev/null
+++ b/addons/website_project/controllers/main.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from collections import OrderedDict
+
+from odoo import http, _
+from odoo.http import request
+
+from odoo.addons.website_portal.controllers.main import website_account
+
+
+class WebsiteAccount(website_account):
+
+ def _prepare_portal_layout_values(self):
+ values = super(WebsiteAccount, self)._prepare_portal_layout_values()
+ project_count = request.env['project.project'].search_count([('privacy_visibility','=','portal')])
+ task_count = request.env['project.task'].search_count([('project_id.privacy_visibility','=','portal')])
+ values.update({
+ 'project_count': project_count,
+ 'task_count': task_count,
+ })
+ return values
+
+ @http.route(['/my/projects', '/my/projects/page/
'], type='http', auth="user", website=True)
+ def my_projects(self, page=1, date_begin=None, date_end=None, sortby=None, **kw):
+ values = self._prepare_portal_layout_values()
+ Project = request.env['project.project']
+
+ sortings = {
+ 'date': {'label': _('Newest'), 'order': 'create_date desc'},
+ 'name': {'label': _('Name'), 'order': 'name'},
+ }
+
+ domain = [('privacy_visibility','=','portal')]
+ order = sortings.get(sortby, sortings['date'])['order']
+
+ # archive groups - Default Group By 'create_date'
+ archive_groups = self._get_archive_groups('project.project', domain)
+ if date_begin and date_end:
+ domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
+ # pager
+ pager = request.website.pager(
+ url="/my/projects",
+ url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby},
+ total=values['project_count'],
+ page=page,
+ step=self._items_per_page
+ )
+
+ # content according to pager and archive selected
+ projects = Project.search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])
+
+ values.update({
+ 'date': date_begin,
+ 'date_end': date_end,
+ 'sortings': sortings,
+ 'sortby': sortby,
+ 'projects': projects,
+ 'page_name': 'project',
+ 'archive_groups': archive_groups,
+ 'default_url': '/my/projects',
+ 'pager': pager
+ })
+ return request.website.render("website_project.my_projects", values)
+
+ @http.route(['/my/project/'], type='http', auth="user", website=True)
+ def my_project(self, project=None, **kw):
+ return request.website.render("website_project.my_project", {'project': project})
+
+ @http.route(['/my/tasks', '/my/tasks/page/'], type='http', auth="user", website=True)
+ def my_tasks(self, page=1, date_begin=None, date_end=None, project=None, sortby=None, **kw):
+ values = self._prepare_portal_layout_values()
+
+ sortings = {
+ 'date': {'label': _('Newest'), 'order': 'create_date desc'},
+ 'name': {'label': _('Name'), 'order': 'name'},
+ 'stage': {'label': _('Stage'), 'order': 'stage_id'},
+ 'update': {'label': _('Last Stage Update'), 'order': 'date_last_stage_update desc'},
+ }
+
+ projects = request.env['project.project'].search([('privacy_visibility', '=', 'portal')])
+
+ project_filters = {
+ 'all': {'label': _('All'), 'domain': []},
+ }
+
+ for proj in projects:
+ project_filters.update({
+ str(proj.id): {'label': proj.name, 'domain': [('project_id', '=', proj.id)]}
+ })
+
+ domain = [('project_id.privacy_visibility', '=', 'portal')]
+ domain += project_filters.get(project, project_filters['all'])['domain']
+ order = sortings.get(sortby, sortings['date'])['order']
+
+ # archive groups - Default Group By 'create_date'
+ archive_groups = self._get_archive_groups('project.task', domain)
+ if date_begin and date_end:
+ domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
+
+ # pager
+ pager = request.website.pager(
+ url="/my/tasks",
+ url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'project': project},
+ total=values['task_count'],
+ page=page,
+ step=self._items_per_page
+ )
+ # content according to pager and archive selected
+ tasks = request.env['project.task'].search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])
+
+ values.update({
+ 'date': date_begin,
+ 'date_end': date_end,
+ 'project_filters': OrderedDict(sorted(project_filters.items())),
+ 'projects': projects,
+ 'project': project,
+ 'sortings': sortings,
+ 'sortby': sortby,
+ 'tasks': tasks,
+ 'page_name': 'task',
+ 'archive_groups': archive_groups,
+ 'default_url': '/my/tasks',
+ 'pager': pager
+ })
+ return request.website.render("website_project.my_tasks", values)
+
+ @http.route(['/my/task/'], type='http', auth="user", website=True)
+ def my_task(self, task=None, **kw):
+ return request.website.render("website_project.my_task", {'task': task, 'user': request.env.user})
diff --git a/addons/website_project/demo/project_demo.xml b/addons/website_project/demo/project_demo.xml
new file mode 100644
index 00000000000..25d4d62ef04
--- /dev/null
+++ b/addons/website_project/demo/project_demo.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ portal
+
+
+
+
+
diff --git a/addons/website_project/security/ir.model.access.csv b/addons/website_project/security/ir.model.access.csv
new file mode 100644
index 00000000000..9ce0c0a8bc5
--- /dev/null
+++ b/addons/website_project/security/ir.model.access.csv
@@ -0,0 +1,6 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_account_analytic_account_portal,account_analytic_account,analytic.model_account_analytic_account,base.group_portal,1,0,0,0
+access_project_tags_portal,project_tags_portal,project.model_project_tags,base.group_portal,1,0,0,0
+access_project_portal,project__portal,project.model_project_project,base.group_portal,1,0,0,0
+access_task_portal,task_portal,project.model_project_task,base.group_portal,1,0,0,0
+access_task_type_portal,task_type_portal,project.model_project_task_type,base.group_portal,1,0,0,0
diff --git a/addons/website_project/security/project_security.xml b/addons/website_project/security/project_security.xml
new file mode 100644
index 00000000000..0e163b77dd2
--- /dev/null
+++ b/addons/website_project/security/project_security.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ Project: portal users: portal and following
+
+ [
+ '&',
+ ('privacy_visibility', '=', 'portal'),
+ ('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
+ ]
+
+
+
+
+ Project/Task: portal users: (portal and following project) or (portal and following task)
+
+ [
+ '|',
+ '&',
+ ('project_id.privacy_visibility', '=', 'portal'),
+ ('project_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
+ '&',
+ ('project_id.privacy_visibility', '=', 'portal'),
+ ('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
+ ]
+
+
+
+
diff --git a/addons/website_project/tests/__init__.py b/addons/website_project/tests/__init__.py
new file mode 100644
index 00000000000..f0019498cd9
--- /dev/null
+++ b/addons/website_project/tests/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import test_access_rights
diff --git a/addons/website_project/tests/test_access_rights.py b/addons/website_project/tests/test_access_rights.py
new file mode 100644
index 00000000000..8f6a551a98a
--- /dev/null
+++ b/addons/website_project/tests/test_access_rights.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from openerp.addons.project.tests.test_access_rights import TestPortalProjectBase
+from odoo.exceptions import AccessError
+from odoo.tools import mute_logger
+
+
+class TestPortalProject(TestPortalProjectBase):
+ @mute_logger('openerp.addons.base.ir.ir_model')
+ def test_portal_project_access_rights(self):
+ pigs = self.project_pigs
+ pigs.write({'privacy_visibility': 'portal'})
+
+ # Do: Alfred reads project -> ok (employee ok public)
+ pigs.sudo(self.user_projectuser).read(['user_id'])
+ # Test: all project tasks visible
+ tasks = self.env['project.task'].sudo(self.user_projectuser).search([('project_id', '=', pigs.id)])
+ self.assertEqual(tasks, self.task_1 | self.task_2 | self.task_3 | self.task_4 | self.task_5 | self.task_6,
+ 'access rights: project user should see all tasks of a portal project')
+
+ # Do: Bert reads project -> crash, no group
+ self.assertRaises(AccessError, pigs.sudo(self.user_noone).read, ['user_id'])
+ # Test: no project task searchable
+ self.assertRaises(AccessError, self.env['project.task'].sudo(self.user_noone).search, [('project_id', '=', pigs.id)])
+
+ # Data: task follower
+ pigs.sudo(self.user_projectmanager).message_subscribe_users(user_ids=[self.user_portal.id])
+ self.task_1.sudo(self.user_projectuser).message_subscribe_users(user_ids=[self.user_portal.id])
+ self.task_3.sudo(self.user_projectuser).message_subscribe_users(user_ids=[self.user_portal.id])
+ # Do: Chell reads project -> ok (portal ok public)
+ pigs.sudo(self.user_portal).read(['user_id'])
+ # Do: Donovan reads project -> ko (public ko portal)
+ self.assertRaises(AccessError, pigs.sudo(self.user_public).read, ['user_id'])
+ # Test: no access right to project.task
+ self.assertRaises(AccessError, self.env['project.task'].sudo(self.user_public).search, [])
+ # Data: task follower cleaning
+ self.task_1.sudo(self.user_projectuser).message_unsubscribe_users(user_ids=[self.user_portal.id])
+ self.task_3.sudo(self.user_projectuser).message_unsubscribe_users(user_ids=[self.user_portal.id])
diff --git a/addons/website_project/views/project_templates.xml b/addons/website_project/views/project_templates.xml
new file mode 100644
index 00000000000..71a5ddc0221
--- /dev/null
+++ b/addons/website_project/views/project_templates.xml
@@ -0,0 +1,295 @@
+
+
+
+
+
+ Projects
+
+
+ Tasks
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ There are no projects.
+
+
+
+
+
+ | Name |
+ |
+
+
+
+
+ |
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Customer
+
+
+
+ Project Manager
+
+
+
+
+
+
+
+
+
+
Message and communication history
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ There are no tasks.
+
+
+
+
+
+ | Task |
+ Stage |
+
+
+
+
+
+ |
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Message and communication history
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_project_issue/__openerp__.py b/addons/website_project_issue/__openerp__.py
index c17e64848f2..685694017e1 100644
--- a/addons/website_project_issue/__openerp__.py
+++ b/addons/website_project_issue/__openerp__.py
@@ -1,22 +1,19 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-
{
'name': 'Website Project Issue',
+ 'category': 'Hidden',
'version': '0.1',
'complexity': 'easy',
'description': """
This module adds project issues inside your account's page on website if project_issue and website_portal are installed.
==================================================================================================
""",
- 'depends': ['project_issue', 'website_portal'],
+ 'depends': ['project_issue', 'website_project'],
'data': [
'security/portal_security.xml',
'security/ir.model.access.csv',
'views/project_issue_templates.xml',
],
- 'installable': True,
'auto_install': True,
- 'category': 'Hidden',
}
diff --git a/addons/website_project_issue/controllers/main.py b/addons/website_project_issue/controllers/main.py
index 4e8b4cd35ba..d81d9f69ff9 100644
--- a/addons/website_project_issue/controllers/main.py
+++ b/addons/website_project_issue/controllers/main.py
@@ -1,58 +1,83 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from openerp import http
+
+from collections import OrderedDict
+
+from openerp import http, _
from openerp.addons.website_portal.controllers.main import website_account
from openerp.http import request
class WebsiteAccount(website_account):
- @http.route()
- def account(self):
- response = super(WebsiteAccount, self).account()
- # TDE FIXME: shouldn't that be mnaged by the access rule itself ?
- # portal projects where you or someone from your company are a follower
- user = request.env.user
- issue_count = request.env['project.issue'].sudo().search_count([
- '&',
- ('project_id.privacy_visibility', '=', 'portal'),
- ('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id, user.partner_id.id])
- ])
- response.qcontext.update({'issue_count': issue_count})
- return response
+
+ def _prepare_portal_layout_values(self):
+ values = super(WebsiteAccount, self)._prepare_portal_layout_values()
+ # domain is needed to hide non portal project for employee
+ issue_count = request.env['project.issue'].search_count([('project_id.privacy_visibility','=','portal')])
+ values.update({
+ 'issue_count': issue_count,
+ })
+ return values
@http.route(['/my/issues', '/my/issues/page/'], type='http', auth="user", website=True)
- def portal_my_issues(self, page=1, date_begin=None, date_end=None, **kw):
+ def my_issues(self, page=1, date_begin=None, date_end=None, project=None, sortby=None, **kw):
values = self._prepare_portal_layout_values()
- ProjectIssue = request.env['project.issue']
- domain = []
+
+ sortings = {
+ 'date': {'label': _('Newest'), 'order': 'create_date desc'},
+ 'name': {'label': _('Name'), 'order': 'name'},
+ 'stage': {'label': _('Stage'), 'order': 'stage_id'},
+ 'update': {'label': _('Last Stage Update'), 'order': 'date_last_stage_update desc'},
+ }
+
+ projects = request.env['project.project'].search([('privacy_visibility','=','portal')])
+
+ project_filters = {
+ 'all': {'label': _('All'), 'domain': []},
+ }
+
+ for proj in projects:
+ project_filters.update({
+ str(proj.id): {'label': proj.name, 'domain': [('project_id', '=', proj.id)]}
+ })
+
+ domain = [('project_id.privacy_visibility','=','portal')]
+ domain += project_filters.get(project, project_filters['all'])['domain']
+ order = sortings.get(sortby, sortings['date'])['order']
+
# archive groups - Default Group By 'create_date'
archive_groups = self._get_archive_groups('project.issue', domain)
if date_begin and date_end:
- domain += [('create_date', '>=', date_begin), ('create_date', '<', date_end)]
+ domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
# pager
- issue_count = ProjectIssue.search_count(domain)
pager = request.website.pager(
url="/my/issues",
url_args={'date_begin': date_begin, 'date_end': date_end},
- total=issue_count,
+ total=values['issue_count'],
page=page,
step=self._items_per_page
)
# content according to pager and archive selected
- project_issues = ProjectIssue.search(domain, order="stage_id", limit=self._items_per_page, offset=pager['offset'])
+ project_issues = request.env['project.issue'].search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])
values.update({
'date': date_begin,
+ 'date_end': date_end,
+ 'project_filters': OrderedDict(sorted(project_filters.items())),
+ 'projects': projects,
+ 'project': project,
+ 'sortings': sortings,
+ 'sortby': sortby,
'issues': project_issues,
'page_name': 'issue',
'archive_groups': archive_groups,
'default_url': '/my/issues',
'pager': pager
})
- return request.website.render("website_project_issue.portal_my_issues", values)
+ return request.website.render("website_project_issue.my_issues", values)
@http.route(['/my/issues/'], type='http', auth="user", website=True)
- def issues_followup(self, issue_id=None, **kw):
+ def my_issues_issue(self, issue_id=None, **kw):
issue = request.env['project.issue'].browse(issue_id)
- return request.website.render("website_project_issue.issues_followup", {'issue': issue})
+ return request.website.render("website_project_issue.my_issues_issue", {'issue': issue})
diff --git a/addons/website_project_issue/security/portal_security.xml b/addons/website_project_issue/security/portal_security.xml
index c30bd029f50..7699392344c 100644
--- a/addons/website_project_issue/security/portal_security.xml
+++ b/addons/website_project_issue/security/portal_security.xml
@@ -1,29 +1,21 @@
-
-
+
+
-
- Project/Issue: portal users: (portal and colleagues following) or (followers and following)
-
- ['|',
- '&',
- ('project_id.privacy_visibility', '=', 'portal'),
- ('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
- '&',
- ('project_id.privacy_visibility', '=', 'followers'),
- ('message_partner_ids', 'in', [user.partner_id.id]),
- ]
-
-
+
+ Project/Issue: portal users: (portal and (following project or following issue)
+
+ [
+ '|',
+ '&',
+ ('project_id.privacy_visibility', '=', 'portal'),
+ ('project_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
+ '&',
+ ('project_id.privacy_visibility', '=', 'portal'),
+ ('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
+ ]
+
+
-
- Project/Issue: employees: portal, employee or (followers and following)
- ['|',
- ('project_id.privacy_visibility', 'in', ['portal', 'employees']),
- '&',
- ('project_id.privacy_visibility', '=', 'followers'),
- ('message_partner_ids', 'in', [user.partner_id.id]),
- ]
-
-
-
+
+
diff --git a/addons/website_project_issue/tests/test_access_rights.py b/addons/website_project_issue/tests/test_access_rights.py
index afa986aa08d..defdd6ff55c 100644
--- a/addons/website_project_issue/tests/test_access_rights.py
+++ b/addons/website_project_issue/tests/test_access_rights.py
@@ -84,18 +84,6 @@ class TestPortalIssue(TestPortalProjectBase):
# ----------------------------------------
self.project_pigs.write({'privacy_visibility': 'followers'})
- # Do: Alfred reads project -> ko (employee ko followers)
- # Test: no project issue visible
- issues = Issue.sudo(self.user_projectuser.id).search([('project_id', '=', pigs_id)])
- self.assertEqual(set(issues.ids), set([self.issue_4.id]),
- 'access rights: employee user should not see issues of a not-followed followers project, only assigned')
-
- # Do: Chell reads project -> ko (portal ko employee)
- # Test: no project issue visible
- issues = Issue.sudo(self.user_portal.id).search([('project_id', '=', pigs_id)])
- self.assertEqual(set(issues.ids), set([self.issue_5.id]),
- 'access rights: portal user should not see issues of a not-followed followers project, only assigned')
-
# Data: subscribe Alfred, Chell and Donovan as follower
self.project_pigs.message_subscribe_users(user_ids=[self.user_projectuser.id, self.user_portal.id, self.user_public.id])
self.issue_1.sudo(self.user_projectmanager.id).message_subscribe_users(user_ids=[self.user_portal.id, self.user_projectuser.id])
@@ -104,11 +92,12 @@ class TestPortalIssue(TestPortalProjectBase):
# Do: Alfred reads project -> ok (follower ok followers)
# Test: followed + assigned issues visible
issues = Issue.sudo(self.user_projectuser.id).search([('project_id', '=', pigs_id)])
- self.assertEqual(set(issues.ids), set([self.issue_1.id, self.issue_3.id, self.issue_4.id]),
+
+ self.assertEqual(set(issues.ids), test_issue_ids,
'access rights: employee user should not see followed + assigned issues of a follower project')
# Do: Chell reads project -> ok (follower ok follower)
# Test: followed + assigned issues visible
issues = Issue.sudo(self.user_portal.id).search([('project_id', '=', pigs_id)])
- self.assertEqual(set(issues.ids), set([self.issue_1.id, self.issue_3.id, self.issue_5.id]),
- 'access rights: employee user should not see followed + assigned issues of a follower project')
+ self.assertFalse(set(issues.ids),
+ 'access rights: portal user should not see issues of a follower project')
diff --git a/addons/website_project_issue/views/project_issue_templates.xml b/addons/website_project_issue/views/project_issue_templates.xml
index a0bdc8338a1..b1a0a93be43 100644
--- a/addons/website_project_issue/views/project_issue_templates.xml
+++ b/addons/website_project_issue/views/project_issue_templates.xml
@@ -1,7 +1,7 @@
-