diff --git a/document_page_approval/__manifest__.py b/document_page_approval/__manifest__.py index 170391bd..57fd9b68 100644 --- a/document_page_approval/__manifest__.py +++ b/document_page_approval/__manifest__.py @@ -2,28 +2,25 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { - 'name': 'Document Page Approval', - 'version': '12.0.1.0.0', + "name": "Document Page Approval", + "version": "12.0.1.0.0", "author": "Savoir-faire Linux, Odoo Community Association (OCA)", "website": "http://www.savoirfairelinux.com", "license": "AGPL-3", - 'category': 'Knowledge Management', - 'depends': [ - 'document_page', - 'mail', + "category": "Knowledge Management", + "depends": ["document_page", "mail",], + "data": [ + "data/email_template.xml", + "views/document_page_approval.xml", + "security/document_page_security.xml", + "security/ir.model.access.csv", ], - 'data': [ - 'data/email_template.xml', - 'views/document_page_approval.xml', - 'security/document_page_security.xml', - 'security/ir.model.access.csv', + "images": [ + "images/category.png", + "images/page_history_list.png", + "images/page_history.png", ], - 'images': [ - 'images/category.png', - 'images/page_history_list.png', - 'images/page_history.png', - ], - 'post_init_hook': 'post_init_hook', - 'uninstall_hook': 'uninstall_hook', - 'installable': True, + "post_init_hook": "post_init_hook", + "uninstall_hook": "uninstall_hook", + "installable": True, } diff --git a/document_page_approval/data/email_template.xml b/document_page_approval/data/email_template.xml index 6118c5f8..deb0ea5a 100644 --- a/document_page_approval/data/email_template.xml +++ b/document_page_approval/data/email_template.xml @@ -1,13 +1,16 @@ - + - Automated new draft need approval Notification Mail - ${object.create_uid.company_id.email or 'noreply@localhost.com'} - New version of ${object.display_name} needs your approval - - + ${object.create_uid.company_id.email or 'noreply@localhost.com'} + New version of ${object.display_name} needs your approval + + ${object.create_uid.partner_id.lang} - diff --git a/document_page_approval/hooks.py b/document_page_approval/hooks.py index 973032d6..0fa1e3b1 100644 --- a/document_page_approval/hooks.py +++ b/document_page_approval/hooks.py @@ -1,25 +1,25 @@ # Copyright 2018 Ivan Todorovich () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import logging + _logger = logging.getLogger(__name__) def post_init_hook(cr, registry): # pragma: no cover # Set all pre-existing pages history to approved - _logger.info('Setting history to approved.') - cr.execute(""" + _logger.info("Setting history to approved.") + cr.execute( + """ UPDATE document_page_history SET state='approved', approved_uid=create_uid, approved_date=create_date WHERE state IS NULL OR state = 'draft' - """) + """ + ) def uninstall_hook(cr, registry): # pragma: no cover # Remove unapproved pages - _logger.info('Deleting unapproved Change Requests.') - cr.execute( - "DELETE FROM document_page_history " - "WHERE state != 'approved'" - ) + _logger.info("Deleting unapproved Change Requests.") + cr.execute("DELETE FROM document_page_history " "WHERE state != 'approved'") diff --git a/document_page_approval/i18n/document_page_approval.pot b/document_page_approval/i18n/document_page_approval.pot index 5192b42e..5768707b 100644 --- a/document_page_approval/i18n/document_page_approval.pot +++ b/document_page_approval/i18n/document_page_approval.pot @@ -262,21 +262,24 @@ msgstr "" #. module: document_page_approval #: code:addons/document_page_approval/models/document_page_history.py:102 #, python-format -msgid "You are not authorized to do this. \n" +msgid "You are not authorized to do this. +\n" "Only approvers with these groups can approve this: " msgstr "" #. module: document_page_approval #: code:addons/document_page_approval/models/document_page_history.py:62 #, python-format -msgid "You are not authorized to do this. \n" +msgid "You are not authorized to do this. +\n" "Only owners or approvers can reopen Change Requests." msgstr "" #. module: document_page_approval #: code:addons/document_page_approval/models/document_page_history.py:79 #, python-format -msgid "You are not authorized to do this. \n" +msgid "You are not authorized to do this. +\n" "Only owners or approvers can request approval." msgstr "" diff --git a/document_page_approval/models/document_page.py b/document_page_approval/models/document_page.py index 44c881c8..3f9e21fb 100644 --- a/document_page_approval/models/document_page.py +++ b/document_page_approval/models/document_page.py @@ -2,77 +2,74 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models from ast import literal_eval +from odoo import api, fields, models + class DocumentPage(models.Model): """Useful to know the state of a document.""" - _inherit = 'document.page' + _inherit = "document.page" history_ids = fields.One2many( - order='approved_date DESC', - domain=[('state', '=', 'approved')], + order="approved_date DESC", domain=[("state", "=", "approved")], ) approved_date = fields.Datetime( - 'Approved Date', - related='history_head.approved_date', + "Approved Date", + related="history_head.approved_date", store=True, index=True, readonly=True, ) approved_uid = fields.Many2one( - 'res.users', - 'Approved by', - related='history_head.approved_uid', + "res.users", + "Approved by", + related="history_head.approved_uid", store=True, index=True, readonly=True, ) approval_required = fields.Boolean( - 'Require approval', - help='Require approval for changes on this page or its child pages.', + "Require approval", + help="Require approval for changes on this page or its child pages.", ) approver_gid = fields.Many2one( "res.groups", "Approver group", - help='Users must also belong to the Approvers group', + help="Users must also belong to the Approvers group", ) is_approval_required = fields.Boolean( - 'Approval required', - help='If true, changes of this page require approval', - compute='_compute_is_approval_required', + "Approval required", + help="If true, changes of this page require approval", + compute="_compute_is_approval_required", ) - am_i_approver = fields.Boolean( - compute='_compute_am_i_approver' - ) + am_i_approver = fields.Boolean(compute="_compute_am_i_approver") approver_group_ids = fields.Many2many( - 'res.groups', - string='Approver groups', - help='Groups that can approve changes to this document', - compute='_compute_approver_group_ids', + "res.groups", + string="Approver groups", + help="Groups that can approve changes to this document", + compute="_compute_approver_group_ids", ) has_changes_pending_approval = fields.Boolean( - compute='_compute_has_changes_pending_approval', - string='Has changes pending approval' + compute="_compute_has_changes_pending_approval", + string="Has changes pending approval", ) user_has_drafts = fields.Boolean( - compute='_compute_user_has_drafts', - string='User has drafts?', + compute="_compute_user_has_drafts", string="User has drafts?", ) @api.multi - @api.depends('approval_required', 'parent_id.is_approval_required') + @api.depends("approval_required", "parent_id.is_approval_required") def _compute_is_approval_required(self): """Check if the document required approval based on his parents.""" for page in self: @@ -82,7 +79,7 @@ class DocumentPage(models.Model): page.is_approval_required = res @api.multi - @api.depends('approver_gid', 'parent_id.approver_group_ids') + @api.depends("approver_gid", "parent_id.approver_group_ids") def _compute_approver_group_ids(self): """Compute the approver groups based on his parents.""" for page in self: @@ -92,7 +89,7 @@ class DocumentPage(models.Model): page.approver_group_ids = res @api.multi - @api.depends('is_approval_required', 'approver_group_ids') + @api.depends("is_approval_required", "approver_group_ids") def _compute_am_i_approver(self): """Check if the current user can approve changes to this page.""" for rec in self: @@ -106,11 +103,10 @@ class DocumentPage(models.Model): if not self.is_approval_required: return True # if user belongs to 'Knowledge / Manager', he can approve anything - if user.has_group('document_page.group_document_manager'): + if user.has_group("document_page.group_document_manager"): return True # to approve, user must have approver rights - if not user.has_group( - 'document_page_approval.group_document_approver_user'): + if not user.has_group("document_page_approval.group_document_approver_user"): return False # if there aren't any approver_groups_defined, user can approve if not self.approver_group_ids: @@ -120,21 +116,21 @@ class DocumentPage(models.Model): @api.multi def _compute_has_changes_pending_approval(self): - history = self.env['document.page.history'] + history = self.env["document.page.history"] for rec in self: - changes = history.search_count([ - ('page_id', '=', rec.id), - ('state', '=', 'to approve')]) - rec.has_changes_pending_approval = (changes > 0) + changes = history.search_count( + [("page_id", "=", rec.id), ("state", "=", "to approve")] + ) + rec.has_changes_pending_approval = changes > 0 @api.multi def _compute_user_has_drafts(self): - history = self.env['document.page.history'] + history = self.env["document.page.history"] for rec in self: - changes = history.search_count([ - ('page_id', '=', rec.id), - ('state', '=', 'draft')]) - rec.user_has_drafts = (changes > 0) + changes = history.search_count( + [("page_id", "=", rec.id), ("state", "=", "draft")] + ) + rec.user_has_drafts = changes > 0 @api.multi def _create_history(self, vals): @@ -144,10 +140,10 @@ class DocumentPage(models.Model): @api.multi def action_changes_pending_approval(self): self.ensure_one() - action = self.env.ref('document_page_approval.action_change_requests') + action = self.env.ref("document_page_approval.action_change_requests") action = action.read()[0] - context = literal_eval(action['context']) - context['search_default_page_id'] = self.id - context['default_page_id'] = self.id - action['context'] = context + context = literal_eval(action["context"]) + context["search_default_page_id"] = self.id + context["default_page_id"] = self.id + action["context"] = context return action diff --git a/document_page_approval/models/document_page_history.py b/document_page_approval/models/document_page_history.py index f836050b..2acf6932 100644 --- a/document_page_approval/models/document_page_history.py +++ b/document_page_approval/models/document_page_history.py @@ -1,90 +1,84 @@ # Copyright (C) 2013 Savoir-faire Linux (). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo.tools.translate import _ from odoo import api, fields, models from odoo.exceptions import UserError +from odoo.tools.translate import _ class DocumentPageHistory(models.Model): """Useful to manage edition's workflow on a document.""" - _name = 'document.page.history' - _inherit = ['document.page.history', 'mail.thread'] + _name = "document.page.history" + _inherit = ["document.page.history", "mail.thread"] - state = fields.Selection([ - ('draft', 'Draft'), - ('to approve', 'Pending Approval'), - ('approved', 'Approved'), - ('cancelled', 'Cancelled')], - 'Status', - default='draft', + state = fields.Selection( + [ + ("draft", "Draft"), + ("to approve", "Pending Approval"), + ("approved", "Approved"), + ("cancelled", "Cancelled"), + ], + "Status", + default="draft", readonly=True, ) - approved_date = fields.Datetime( - 'Approved Date', - ) + approved_date = fields.Datetime("Approved Date",) - approved_uid = fields.Many2one( - 'res.users', - 'Approved by', - ) + approved_uid = fields.Many2one("res.users", "Approved by",) is_approval_required = fields.Boolean( - related='page_id.is_approval_required', - string="Approval required", + related="page_id.is_approval_required", string="Approval required", ) - am_i_owner = fields.Boolean( - compute='_compute_am_i_owner' - ) + am_i_owner = fields.Boolean(compute="_compute_am_i_owner") - am_i_approver = fields.Boolean( - related='page_id.am_i_approver', - related_sudo=False, - ) + am_i_approver = fields.Boolean(related="page_id.am_i_approver", related_sudo=False,) - page_url = fields.Text( - compute='_compute_page_url', - string="URL", - ) + page_url = fields.Text(compute="_compute_page_url", string="URL",) @api.multi def action_draft(self): """Set a change request as draft""" for rec in self: - if not rec.state == 'cancelled': - raise UserError( - _('You need to cancel it before reopening.')) + if not rec.state == "cancelled": + raise UserError(_("You need to cancel it before reopening.")) if not (rec.am_i_owner or rec.am_i_approver): raise UserError( - _('You are not authorized to do this.\r\n' - 'Only owners or approvers can reopen Change Requests.')) - rec.write({'state': 'draft'}) + _( + "You are not authorized to do this.\r\n" + "Only owners or approvers can reopen Change Requests." + ) + ) + rec.write({"state": "draft"}) @api.multi def action_to_approve(self): """Set a change request as to approve""" template = self.env.ref( - 'document_page_approval.email_template_new_draft_need_approval') + "document_page_approval.email_template_new_draft_need_approval" + ) approver_gid = self.env.ref( - 'document_page_approval.group_document_approver_user') + "document_page_approval.group_document_approver_user" + ) for rec in self: - if rec.state != 'draft': - raise UserError( - _("Can't approve pages in '%s' state.") % rec.state) + if rec.state != "draft": + raise UserError(_("Can't approve pages in '%s' state.") % rec.state) if not (rec.am_i_owner or rec.am_i_approver): raise UserError( - _('You are not authorized to do this.\r\n' - 'Only owners or approvers can request approval.')) + _( + "You are not authorized to do this.\r\n" + "Only owners or approvers can request approval." + ) + ) # request approval if rec.is_approval_required: - rec.write({'state': 'to approve'}) + rec.write({"state": "to approve"}) guids = [g.id for g in rec.page_id.approver_group_ids] - users = self.env['res.users'].search([ - ('groups_id', 'in', guids), - ('groups_id', 'in', approver_gid.id)]) + users = self.env["res.users"].search( + [("groups_id", "in", guids), ("groups_id", "in", approver_gid.id)] + ) rec.message_subscribe([u.id for u in users]) rec.message_post_with_template(template.id) else: @@ -95,49 +89,49 @@ class DocumentPageHistory(models.Model): def action_approve(self): """Set a change request as approved.""" for rec in self: - if rec.state not in ['draft', 'to approve']: - raise UserError( - _("Can't approve page in '%s' state.") % rec.state) + if rec.state not in ["draft", "to approve"]: + raise UserError(_("Can't approve page in '%s' state.") % rec.state) if not rec.am_i_approver: - raise UserError(_( - 'You are not authorized to do this.\r\n' - 'Only approvers with these groups can approve this: ' - ) % ', '.join( - [g.display_name - for g in rec.page_id.approver_group_ids])) + raise UserError( + _( + "You are not authorized to do this.\r\n" + "Only approvers with these groups can approve this: " + ) + % ", ".join( + [g.display_name for g in rec.page_id.approver_group_ids] + ) + ) # Update state - rec.write({ - 'state': 'approved', - 'approved_date': fields.datetime.now(), - 'approved_uid': self.env.uid, - }) + rec.write( + { + "state": "approved", + "approved_date": fields.datetime.now(), + "approved_uid": self.env.uid, + } + ) # Trigger computed field update rec.page_id._compute_history_head() # Notify state change rec.message_post( - subtype='mt_comment', - body=_( - 'Change request has been approved by %s.' - ) % (self.env.user.name) + subtype="mt_comment", + body=_("Change request has been approved by %s.") + % (self.env.user.name), ) # Notify followers a new version is available rec.page_id.message_post( - subtype='mt_comment', - body=_( - 'New version of the document %s approved.' - ) % (rec.page_id.name) + subtype="mt_comment", + body=_("New version of the document %s approved.") % (rec.page_id.name), ) @api.multi def action_cancel(self): """Set a change request as cancelled.""" - self.write({'state': 'cancelled'}) + self.write({"state": "cancelled"}) for rec in self: rec.message_post( - subtype='mt_comment', - body=_( - 'Change request %s has been cancelled by %s.' - ) % (rec.display_name, self.env.user.name) + subtype="mt_comment", + body=_("Change request %s has been cancelled by %s.") + % (rec.display_name, self.env.user.name), ) @api.multi @@ -150,36 +144,31 @@ class DocumentPageHistory(models.Model): def _compute_am_i_owner(self): """Check if current user is the owner""" for rec in self: - rec.am_i_owner = (rec.create_uid == self.env.user) + rec.am_i_owner = rec.create_uid == self.env.user @api.multi def _compute_page_url(self): """Compute the page url.""" for page in self: - base_url = self.env['ir.config_parameter'].sudo().get_param( - 'web.base.url', - default='http://localhost:8069' + base_url = ( + self.env["ir.config_parameter"] + .sudo() + .get_param("web.base.url", default="http://localhost:8069") ) page.page_url = ( - '{}/web#db={}&id={}&view_type=form&' - 'model=document.page.history').format( - base_url, - self.env.cr.dbname, - page.id - ) + "{}/web#db={}&id={}&view_type=form&" "model=document.page.history" + ).format(base_url, self.env.cr.dbname, page.id) @api.multi def _compute_diff(self): """Shows a diff between this version and the previous version""" - history = self.env['document.page.history'] + history = self.env["document.page.history"] for rec in self: - domain = [ - ('page_id', '=', rec.page_id.id), - ('state', '=', 'approved')] + domain = [("page_id", "=", rec.page_id.id), ("state", "=", "approved")] if rec.approved_date: - domain.append(('approved_date', '<', rec.approved_date)) - prev = history.search(domain, limit=1, order='approved_date DESC') + domain.append(("approved_date", "<", rec.approved_date)) + prev = history.search(domain, limit=1, order="approved_date DESC") if prev: rec.diff = self._get_diff(prev.id, rec.id) else: diff --git a/document_page_approval/security/document_page_security.xml b/document_page_approval/security/document_page_security.xml index 03a170e4..f0c34538 100644 --- a/document_page_approval/security/document_page_security.xml +++ b/document_page_approval/security/document_page_security.xml @@ -1,36 +1,36 @@ - + - Approver - - + + - - + - Change Request Global - - - ['|',('state','=','approved'),('create_uid','=',user.id)] - - - - + + + ['|',('state','=','approved'),('create_uid','=',user.id)] + + + + - Change Request Approver - - + + [('state','!=','draft')] - - - - + + + + - diff --git a/document_page_approval/tests/test_document_page_approval.py b/document_page_approval/tests/test_document_page_approval.py index f41cc6ef..55e373c4 100644 --- a/document_page_approval/tests/test_document_page_approval.py +++ b/document_page_approval/tests/test_document_page_approval.py @@ -2,31 +2,35 @@ from odoo.tests import common class TestDocumentPageApproval(common.TransactionCase): - def setUp(self): super(TestDocumentPageApproval, self).setUp() - self.page_obj = self.env['document.page'] - self.history_obj = self.env['document.page.history'] + self.page_obj = self.env["document.page"] + self.history_obj = self.env["document.page.history"] # demo - self.category1 = self.env.ref('document_page.demo_category1') - self.page1 = self.env.ref('document_page.demo_page1') + self.category1 = self.env.ref("document_page.demo_category1") + self.page1 = self.env.ref("document_page.demo_page1") self.approver_gid = self.env.ref( - 'document_page_approval.group_document_approver_user') - self.env.ref('base.user_root').write({ - 'groups_id': [(4, self.approver_gid.id)], - }) + "document_page_approval.group_document_approver_user" + ) + self.env.ref("base.user_root").write( + {"groups_id": [(4, self.approver_gid.id)],} + ) # demo_approval - self.category2 = self.page_obj.create({ - 'name': 'This category requires approval', - 'type': 'category', - 'approval_required': True, - 'approver_gid': self.approver_gid.id, - }) - self.page2 = self.page_obj.create({ - 'name': 'This page requires approval', - 'parent_id': self.category2.id, - 'content': 'This content will require approval', - }) + self.category2 = self.page_obj.create( + { + "name": "This category requires approval", + "type": "category", + "approval_required": True, + "approver_gid": self.approver_gid.id, + } + ) + self.page2 = self.page_obj.create( + { + "name": "This page requires approval", + "parent_id": self.category2.id, + "content": "This content will require approval", + } + ) def test_approval_required(self): page = self.page2 @@ -36,13 +40,12 @@ class TestDocumentPageApproval(common.TransactionCase): def test_change_request_approve(self): page = self.page2 - chreq = self.history_obj.search([ - ('page_id', '=', page.id), - ('state', '!=', 'approved') - ])[0] + chreq = self.history_obj.search( + [("page_id", "=", page.id), ("state", "!=", "approved")] + )[0] # It should automatically be in 'to approve' state - self.assertEqual(chreq.state, 'to approve') + self.assertEqual(chreq.state, "to approve") self.assertNotEqual(chreq.content, page.content) # who_am_i @@ -51,66 +54,66 @@ class TestDocumentPageApproval(common.TransactionCase): # approve chreq.action_approve() - self.assertEqual(chreq.state, 'approved') + self.assertEqual(chreq.state, "approved") self.assertEqual(chreq.content, page.content) # new changes should create change requests - page.write({'content': 'New content'}) - self.assertNotEqual(page.content, 'New content') - chreq = self.history_obj.search([ - ('page_id', '=', page.id), - ('state', '!=', 'approved') - ])[0] + page.write({"content": "New content"}) + self.assertNotEqual(page.content, "New content") + chreq = self.history_obj.search( + [("page_id", "=", page.id), ("state", "!=", "approved")] + )[0] chreq.action_approve() - self.assertEqual(page.content, 'New content') + self.assertEqual(page.content, "New content") def test_change_request_auto_approve(self): page = self.page1 self.assertFalse(page.is_approval_required) - page.write({'content': 'New content'}) - self.assertEqual(page.content, 'New content') + page.write({"content": "New content"}) + self.assertEqual(page.content, "New content") def test_change_request_from_scratch(self): page = self.page2 # aprove everything - self.history_obj.search([ - ('page_id', '=', page.id), - ('state', '!=', 'approved') - ]).action_approve() + self.history_obj.search( + [("page_id", "=", page.id), ("state", "!=", "approved")] + ).action_approve() # new change request from scrath - chreq = self.history_obj.create({ - 'page_id': page.id, - 'summary': 'Changed something', - 'content': 'New content', - }) + chreq = self.history_obj.create( + { + "page_id": page.id, + "summary": "Changed something", + "content": "New content", + } + ) - self.assertEqual(chreq.state, 'draft') + self.assertEqual(chreq.state, "draft") self.assertNotEqual(page.content, chreq.content) self.assertNotEqual(page.approved_date, chreq.approved_date) self.assertNotEqual(page.approved_uid, chreq.approved_uid) chreq.action_to_approve() - self.assertEqual(chreq.state, 'to approve') + self.assertEqual(chreq.state, "to approve") self.assertNotEqual(page.content, chreq.content) self.assertNotEqual(page.approved_date, chreq.approved_date) self.assertNotEqual(page.approved_uid, chreq.approved_uid) chreq.action_cancel() - self.assertEqual(chreq.state, 'cancelled') + self.assertEqual(chreq.state, "cancelled") self.assertNotEqual(page.content, chreq.content) self.assertNotEqual(page.approved_date, chreq.approved_date) self.assertNotEqual(page.approved_uid, chreq.approved_uid) chreq.action_draft() - self.assertEqual(chreq.state, 'draft') + self.assertEqual(chreq.state, "draft") self.assertNotEqual(page.content, chreq.content) self.assertNotEqual(page.approved_date, chreq.approved_date) self.assertNotEqual(page.approved_uid, chreq.approved_uid) chreq.action_approve() - self.assertEqual(chreq.state, 'approved') + self.assertEqual(chreq.state, "approved") self.assertEqual(page.content, chreq.content) self.assertEqual(page.approved_date, chreq.approved_date) self.assertEqual(page.approved_uid, chreq.approved_uid) @@ -122,6 +125,6 @@ class TestDocumentPageApproval(common.TransactionCase): def test_get_page_url(self): """Test if page url exist.""" - pages = self.env['document.page.history'].search([]) + pages = self.env["document.page.history"].search([]) page = pages[0] self.assertIsNotNone(page.page_url) diff --git a/document_page_approval/views/document_page_approval.xml b/document_page_approval/views/document_page_approval.xml index 105c73e5..16225ba2 100644 --- a/document_page_approval/views/document_page_approval.xml +++ b/document_page_approval/views/document_page_approval.xml @@ -1,124 +1,196 @@ - + - document.page.history.form document.page.history - +
-
- - + + - {'readonly': [('state', 'not in', ['draft'])]} - {'readonly': [('state', 'not in', ['draft'])]} - {'readonly': [('state', 'not in', ['draft'])]} - {'readonly': [('state', 'not in', ['draft'])]} + + {'readonly': [('state', 'not in', ['draft'])]} + + + {'readonly': [('state', 'not in', ['draft'])]} + + + {'readonly': [('state', 'not in', ['draft'])]} + + + {'readonly': [('state', 'not in', ['draft'])]} +
- - + +
- document.page.history.form document.page.history - - + + - {'readonly': False} - {'readonly': False} + + {'readonly': False} + + + {'readonly': False} + - document.page.form document.page - -