[IMP] website_project_issue: Portal access to Issues

Move portal features (mainly ir.rule) from project_issue to
website_project_issue.

Remove backend menu item, issues should be accessed from the website_portal My
account page, the backend portal menuitem is removed.

Add the dependency on website_portal to display the issue count on projects.

Original authors: Vipul Bhatt, Florian Wintjens
This commit is contained in:
Antony Lesuisse
2016-07-06 01:17:43 +02:00
parent ccf606e026
commit 30bc0ee076
8 changed files with 153 additions and 141 deletions
@@ -19,8 +19,10 @@ class Project(models.Model):
res.append(("project.issue", "Issues"))
return res
@api.multi
def _compute_issue_count(self):
self.issue_count = self.env['project.issue'].search_count([('project_id', '=', self.id), '|', ('stage_id.fold', '=', False), ('stage_id', '=', False)])
for project in self:
project.issue_count = self.env['project.issue'].search_count([('project_id', '=', project.id), '|', ('stage_id.fold', '=', False), ('stage_id', '=', False)])
def _issue_needaction_count(self):
issue_data = self.env['project.issue'].read_group([('project_id', 'in', self.ids), ('message_needaction', '=', True)], ['project_id'], ['project_id'])
@@ -10,40 +10,16 @@
</record>
<record id="issue_user_rule" model="ir.rule">
<field name="name">Project/Issue: employees: employee or (followers and following)</field>
<field name="name">Project/Issue: employees: no need to follow or (following project or following issue</field>
<field name="model_id" ref="model_project_issue"/>
<field name="domain_force">['|',
('project_id.privacy_visibility', '=', 'employees'),
'&amp;',
('project_id.privacy_visibility', '=', 'followers'),
('project_id.privacy_visibility', '!=', 'followers'),
'|',
('project_id.message_partner_ids', 'in', [user.partner_id.id]),
('message_partner_ids', 'in', [user.partner_id.id]),
]</field>
<field name="groups" eval="[(4,ref('base.group_user'))]"/>
</record>
<record id="portal_issue_rule" model="ir.rule">
<field name="name">Project/Issue: portal users: (portal and colleagues following) or (followers and following)</field>
<field name="model_id" ref="project_issue.model_project_issue"/>
<field name="domain_force">['|',
'&amp;',
('project_id.privacy_visibility', '=', 'portal'),
('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('project_id.privacy_visibility', '=', 'followers'),
('message_partner_ids', 'in', [user.partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
<record model="ir.rule" id="project_issue.issue_user_rule">
<field name="name">Project/Issue: employees: portal, employee or (followers and following)</field>
<field name="domain_force">['|',
('project_id.privacy_visibility', 'in', ['portal', 'employees']),
'&amp;',
('project_id.privacy_visibility', '=', 'followers'),
('message_partner_ids', 'in', [user.partner_id.id]),
]</field>
</record>
</data>
</odoo>
@@ -267,26 +267,4 @@
</p>
</field>
</record>
<!-- Portal -->
<record id="open_view_issue" model="ir.actions.act_window">
<field name="name">Issues</field>
<field name="res_model">project.issue</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="project_issue.project_issue_tree_view"/>
<field name="domain" eval=""/>
<field name="context">{"search_default_user_id":'', "search_default_draft":'', "search_default_todo":'', "portal":'True'}</field>
<field name="search_view_id" ref="project_issue.view_project_issue_filter"/>
<field name="target">current</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create an issue.
</p><p>
You can track your issues from this menu and the action we will take.
</p>
</field>
</record>
<menuitem id="portal_services_issues" name="Issues" parent="portal.portal_projects"
action="open_view_issue" sequence="10"/>
</odoo>
+3 -6
View File
@@ -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/project_issue_security.xml',
'security/ir.model.access.csv',
'views/project_issue_templates.xml',
],
'installable': True,
'auto_install': True,
'category': 'Hidden',
}
@@ -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/<int: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/<int:issue_id>'], 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})
@@ -1,29 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<odoo>
<data noupdate="1">
<record id="portal_issue_rule" model="ir.rule">
<field name="name">Project/Issue: portal users: (portal and colleagues following) or (followers and following)</field>
<field name="model_id" ref="project_issue.model_project_issue"/>
<field name="domain_force">['|',
'&amp;',
('project_id.privacy_visibility', '=', 'portal'),
('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('project_id.privacy_visibility', '=', 'followers'),
('message_partner_ids', 'in', [user.partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
<record id="portal_issue_rule" model="ir.rule">
<field name="name">Project/Issue: portal users: (portal and (following project or following issue)</field>
<field name="model_id" ref="project_issue.model_project_issue"/>
<field name="domain_force">[
'|',
'&amp;',
('project_id.privacy_visibility', '=', 'portal'),
('project_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('project_id.privacy_visibility', '=', 'portal'),
('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
<record model="ir.rule" id="project_issue.issue_user_rule">
<field name="name">Project/Issue: employees: portal, employee or (followers and following)</field>
<field name="domain_force">['|',
('project_id.privacy_visibility', 'in', ['portal', 'employees']),
'&amp;',
('project_id.privacy_visibility', '=', 'followers'),
('message_partner_ids', 'in', [user.partner_id.id]),
]</field>
</record>
</data>
</openerp>
</data>
</odoo>
@@ -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')
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="portal_my_home_menu_issue" name="Portal layout : issues menu entry" inherit_id="website_portal.portal_layout" priority="30">
<template id="portal_layout" name="Portal layout: Issues menu entry" inherit_id="website_portal.portal_layout" priority="30">
<xpath expr="//ul[contains(@class,'o_portal_submenu')]" position="inside">
<li t-att-class="page_name == 'issue' and 'active' or ''">
<a href="/my/issues">Issues</a>
@@ -9,7 +9,7 @@
</xpath>
</template>
<template id="portal_my_home_issue" name="Portal My Home : issue entries" inherit_id="website_portal.portal_my_home" priority="30">
<template id="portal_my_home" name="Portal My Home: Issue entries" inherit_id="website_portal.portal_my_home" priority="30">
<xpath expr="//div[contains(@class,'o_my_home_content')]" position="inside">
<h3 class="page-header">
<a href="/my/issues">Your Issues
@@ -26,39 +26,67 @@
</xpath>
</template>
<template id="portal_my_issues" name="My Issues" priority="30">
<template id="my_issues" name="My Issues">
<t t-call="website_portal.portal_layout">
<h3 class="page-header">Your Issues</h3>
<t t-if="not issues">
<p>There are currently no issues for your account.</p>
</t>
<t t-if="issues">
<table class="table table-hover status_table">
<h3 class="page-header">
Your Issues
<div class="dropdown pull-right mr8">
<button id="sortby" class="btn btn-default" type="button" data-toggle="dropdown">
<span class="fa fa-sort" /> <span t-esc="sortings.get(sortby, {}).get('label', 'Newest')"/>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="sortby">
<li t-foreach="sortings" t-as="option" t-att-class="sortby == option and 'active'">
<a t-att-href="default_url + '?' + keep_query('date_begin', 'date_end', 'project', sortby=option)"><span t-esc="sortings.get(option).get('label')"/></a>
</li>
</ul>
</div>
<div class="dropdown pull-right mr8">
<button id="project_filters" class="btn btn-default" type="button" data-toggle="dropdown">
Projects: <span t-esc="project_filters.get(project, {}).get('label', 'All')"/>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="project_filters">
<li t-foreach="project_filters" t-as="option" t-att-class="project == option and 'active'">
<a t-att-href="default_url + '?' + keep_query('date_begin', 'date_end', 'sortby', project=option)"><span t-esc="project_filters.get(option).get('label')"/></a>
</li>
</ul>
</div>
</h3>
<div class="panel panel-default">
<t t-if="not issues">
<div class="alert alert-warning mt8" role="alert">
There are no issues.
</div>
</t>
<table t-if="issues" class="table table-hover status_table">
<thead>
<tr class="active">
<th>Issue</th>
<th>Description</th><th/>
<th>Status</th>
<th class="col-md-8">Description</th>
<th>Stage</th>
</tr>
</thead>
<t t-foreach="issues" t-as="issue">
<tr>
<td>
<a t-attf-href="/my/issues/#{issue.id}?{{ keep_query() }}">Issue <t t-esc="issue.id"/></a>
<a t-attf-href="/my/issues/#{issue.id}?{{ keep_query() }}">Issue <t t-esc="issue.id" /></a>
</td>
<td><span t-field="issue.name"/></td>
<td>
<span class="label label-info" title="Current stage of the issue" t-esc="issue.stage_id.name" />
</td>
<td><span t-field="issue.name"/></td><td/>
<td><span t-field="issue.stage_id.name"/></td>
</tr>
</t>
</table>
<div t-if="pager" class="o_portal_pager text-center">
<t t-call="website.pager"/>
</div>
</t>
</div>
<div t-if="pager" class="o_portal_pager text-center">
<t t-call="website.pager"/>
</div>
</t>
</template>
<template id="issues_followup" name="Project Issues">
<template id="my_issues_issue" name="My Issue">
<t t-call="website.layout">
<div id="wrap">
<div class="container">
@@ -122,4 +150,29 @@
</div>
</t>
</template>
<template id="my_projects" inherit_id="website_project.my_projects">
<xpath expr="//table/thead/tr/th[last()]" position="after">
<th></th>
</xpath>
<xpath expr="//table/tbody/tr/td[last()]" position="after">
<td>
<a t-if="project.use_issues" t-attf-href="/my/issues?project=#{project.id}">
<t t-esc="project.issue_count" />
<t t-esc="project.label_issues" />
</a>
</td>
</xpath>
</template>
<template id="my_project" inherit_id="website_project.my_project">
<xpath expr="//div[@class='panel-body']/div[@class='row']/div[@class='col-md-6'][last()]/div[@class='pull-right']" position="before">
<a t-attf-href="/my/issues?project=#{project.id}" class="btn btn-default btn-lg pull-right ml8">
<span class="fa fa-bug" />
<span t-esc="project.issue_count" />
<span t-field="project.label_issues" />
</a>
</xpath>
</template>
</odoo>