[IMP] document_page_approval: black, isort

This commit is contained in:
flachica 2020-07-04 19:11:38 +02:00 committed by Bhavesh Heliconia
parent e1727575df
commit 8d8c8ac32a
9 changed files with 416 additions and 332 deletions

View File

@ -2,28 +2,25 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Document Page Approval', "name": "Document Page Approval",
'version': '12.0.1.0.0', "version": "12.0.1.0.0",
"author": "Savoir-faire Linux, Odoo Community Association (OCA)", "author": "Savoir-faire Linux, Odoo Community Association (OCA)",
"website": "http://www.savoirfairelinux.com", "website": "http://www.savoirfairelinux.com",
"license": "AGPL-3", "license": "AGPL-3",
'category': 'Knowledge Management', "category": "Knowledge Management",
'depends': [ "depends": ["document_page", "mail",],
'document_page', "data": [
'mail', "data/email_template.xml",
"views/document_page_approval.xml",
"security/document_page_security.xml",
"security/ir.model.access.csv",
], ],
'data': [ "images": [
'data/email_template.xml', "images/category.png",
'views/document_page_approval.xml', "images/page_history_list.png",
'security/document_page_security.xml', "images/page_history.png",
'security/ir.model.access.csv',
], ],
'images': [ "post_init_hook": "post_init_hook",
'images/category.png', "uninstall_hook": "uninstall_hook",
'images/page_history_list.png', "installable": True,
'images/page_history.png',
],
'post_init_hook': 'post_init_hook',
'uninstall_hook': 'uninstall_hook',
'installable': True,
} }

View File

@ -1,13 +1,16 @@
<?xml version="1.0"?> <?xml version="1.0" ?>
<odoo> <odoo>
<!-- If user wants to make upgrade-proof customizations to email templates, he should edit ir.model.data and check noupdate himself --> <!-- If user wants to make upgrade-proof customizations to email templates, he should edit ir.model.data and check noupdate himself -->
<record id="email_template_new_draft_need_approval" model="mail.template"> <record id="email_template_new_draft_need_approval" model="mail.template">
<field name="name">Automated new draft need approval Notification Mail</field> <field name="name">Automated new draft need approval Notification Mail</field>
<field name="email_from">${object.create_uid.company_id.email or 'noreply@localhost.com'}</field> <field
<field name="subject">New version of ${object.display_name} needs your approval</field> name="email_from"
<field name="model_id" ref="model_document_page_history"/> >${object.create_uid.company_id.email or 'noreply@localhost.com'}</field>
<field name="auto_delete" eval="True"/> <field
name="subject"
>New version of ${object.display_name} needs your approval</field>
<field name="model_id" ref="model_document_page_history" />
<field name="auto_delete" eval="True" />
<field name="lang">${object.create_uid.partner_id.lang}</field> <field name="lang">${object.create_uid.partner_id.lang}</field>
<field name="body_html"> <field name="body_html">
<![CDATA[ <![CDATA[
@ -39,5 +42,4 @@ ${object.diff|safe}
]]> ]]>
</field> </field>
</record> </record>
</odoo> </odoo>

View File

@ -1,25 +1,25 @@
# Copyright 2018 Ivan Todorovich (<ivan.todorovich@gmail.com>) # Copyright 2018 Ivan Todorovich (<ivan.todorovich@gmail.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging import logging
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
def post_init_hook(cr, registry): # pragma: no cover def post_init_hook(cr, registry): # pragma: no cover
# Set all pre-existing pages history to approved # Set all pre-existing pages history to approved
_logger.info('Setting history to approved.') _logger.info("Setting history to approved.")
cr.execute(""" cr.execute(
"""
UPDATE document_page_history UPDATE document_page_history
SET state='approved', SET state='approved',
approved_uid=create_uid, approved_uid=create_uid,
approved_date=create_date approved_date=create_date
WHERE state IS NULL OR state = 'draft' WHERE state IS NULL OR state = 'draft'
""") """
)
def uninstall_hook(cr, registry): # pragma: no cover def uninstall_hook(cr, registry): # pragma: no cover
# Remove unapproved pages # Remove unapproved pages
_logger.info('Deleting unapproved Change Requests.') _logger.info("Deleting unapproved Change Requests.")
cr.execute( cr.execute("DELETE FROM document_page_history " "WHERE state != 'approved'")
"DELETE FROM document_page_history "
"WHERE state != 'approved'"
)

View File

@ -262,21 +262,24 @@ msgstr ""
#. module: document_page_approval #. module: document_page_approval
#: code:addons/document_page_approval/models/document_page_history.py:102 #: code:addons/document_page_approval/models/document_page_history.py:102
#, python-format #, 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: " "Only approvers with these groups can approve this: "
msgstr "" msgstr ""
#. module: document_page_approval #. module: document_page_approval
#: code:addons/document_page_approval/models/document_page_history.py:62 #: code:addons/document_page_approval/models/document_page_history.py:62
#, python-format #, 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." "Only owners or approvers can reopen Change Requests."
msgstr "" msgstr ""
#. module: document_page_approval #. module: document_page_approval
#: code:addons/document_page_approval/models/document_page_history.py:79 #: code:addons/document_page_approval/models/document_page_history.py:79
#, python-format #, 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." "Only owners or approvers can request approval."
msgstr "" msgstr ""

View File

@ -2,77 +2,74 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
from ast import literal_eval from ast import literal_eval
from odoo import api, fields, models
class DocumentPage(models.Model): class DocumentPage(models.Model):
"""Useful to know the state of a document.""" """Useful to know the state of a document."""
_inherit = 'document.page' _inherit = "document.page"
history_ids = fields.One2many( history_ids = fields.One2many(
order='approved_date DESC', order="approved_date DESC", domain=[("state", "=", "approved")],
domain=[('state', '=', 'approved')],
) )
approved_date = fields.Datetime( approved_date = fields.Datetime(
'Approved Date', "Approved Date",
related='history_head.approved_date', related="history_head.approved_date",
store=True, store=True,
index=True, index=True,
readonly=True, readonly=True,
) )
approved_uid = fields.Many2one( approved_uid = fields.Many2one(
'res.users', "res.users",
'Approved by', "Approved by",
related='history_head.approved_uid', related="history_head.approved_uid",
store=True, store=True,
index=True, index=True,
readonly=True, readonly=True,
) )
approval_required = fields.Boolean( approval_required = fields.Boolean(
'Require approval', "Require approval",
help='Require approval for changes on this page or its child pages.', help="Require approval for changes on this page or its child pages.",
) )
approver_gid = fields.Many2one( approver_gid = fields.Many2one(
"res.groups", "res.groups",
"Approver group", "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( is_approval_required = fields.Boolean(
'Approval required', "Approval required",
help='If true, changes of this page require approval', help="If true, changes of this page require approval",
compute='_compute_is_approval_required', compute="_compute_is_approval_required",
) )
am_i_approver = fields.Boolean( am_i_approver = fields.Boolean(compute="_compute_am_i_approver")
compute='_compute_am_i_approver'
)
approver_group_ids = fields.Many2many( approver_group_ids = fields.Many2many(
'res.groups', "res.groups",
string='Approver groups', string="Approver groups",
help='Groups that can approve changes to this document', help="Groups that can approve changes to this document",
compute='_compute_approver_group_ids', compute="_compute_approver_group_ids",
) )
has_changes_pending_approval = fields.Boolean( has_changes_pending_approval = fields.Boolean(
compute='_compute_has_changes_pending_approval', compute="_compute_has_changes_pending_approval",
string='Has changes pending approval' string="Has changes pending approval",
) )
user_has_drafts = fields.Boolean( user_has_drafts = fields.Boolean(
compute='_compute_user_has_drafts', compute="_compute_user_has_drafts", string="User has drafts?",
string='User has drafts?',
) )
@api.multi @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): def _compute_is_approval_required(self):
"""Check if the document required approval based on his parents.""" """Check if the document required approval based on his parents."""
for page in self: for page in self:
@ -82,7 +79,7 @@ class DocumentPage(models.Model):
page.is_approval_required = res page.is_approval_required = res
@api.multi @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): def _compute_approver_group_ids(self):
"""Compute the approver groups based on his parents.""" """Compute the approver groups based on his parents."""
for page in self: for page in self:
@ -92,7 +89,7 @@ class DocumentPage(models.Model):
page.approver_group_ids = res page.approver_group_ids = res
@api.multi @api.multi
@api.depends('is_approval_required', 'approver_group_ids') @api.depends("is_approval_required", "approver_group_ids")
def _compute_am_i_approver(self): def _compute_am_i_approver(self):
"""Check if the current user can approve changes to this page.""" """Check if the current user can approve changes to this page."""
for rec in self: for rec in self:
@ -106,11 +103,10 @@ class DocumentPage(models.Model):
if not self.is_approval_required: if not self.is_approval_required:
return True return True
# if user belongs to 'Knowledge / Manager', he can approve anything # 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 return True
# to approve, user must have approver rights # to approve, user must have approver rights
if not user.has_group( if not user.has_group("document_page_approval.group_document_approver_user"):
'document_page_approval.group_document_approver_user'):
return False return False
# if there aren't any approver_groups_defined, user can approve # if there aren't any approver_groups_defined, user can approve
if not self.approver_group_ids: if not self.approver_group_ids:
@ -120,21 +116,21 @@ class DocumentPage(models.Model):
@api.multi @api.multi
def _compute_has_changes_pending_approval(self): def _compute_has_changes_pending_approval(self):
history = self.env['document.page.history'] history = self.env["document.page.history"]
for rec in self: for rec in self:
changes = history.search_count([ changes = history.search_count(
('page_id', '=', rec.id), [("page_id", "=", rec.id), ("state", "=", "to approve")]
('state', '=', 'to approve')]) )
rec.has_changes_pending_approval = (changes > 0) rec.has_changes_pending_approval = changes > 0
@api.multi @api.multi
def _compute_user_has_drafts(self): def _compute_user_has_drafts(self):
history = self.env['document.page.history'] history = self.env["document.page.history"]
for rec in self: for rec in self:
changes = history.search_count([ changes = history.search_count(
('page_id', '=', rec.id), [("page_id", "=", rec.id), ("state", "=", "draft")]
('state', '=', 'draft')]) )
rec.user_has_drafts = (changes > 0) rec.user_has_drafts = changes > 0
@api.multi @api.multi
def _create_history(self, vals): def _create_history(self, vals):
@ -144,10 +140,10 @@ class DocumentPage(models.Model):
@api.multi @api.multi
def action_changes_pending_approval(self): def action_changes_pending_approval(self):
self.ensure_one() 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] action = action.read()[0]
context = literal_eval(action['context']) context = literal_eval(action["context"])
context['search_default_page_id'] = self.id context["search_default_page_id"] = self.id
context['default_page_id'] = self.id context["default_page_id"] = self.id
action['context'] = context action["context"] = context
return action return action

View File

@ -1,90 +1,84 @@
# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>). # Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # 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 import api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tools.translate import _
class DocumentPageHistory(models.Model): class DocumentPageHistory(models.Model):
"""Useful to manage edition's workflow on a document.""" """Useful to manage edition's workflow on a document."""
_name = 'document.page.history' _name = "document.page.history"
_inherit = ['document.page.history', 'mail.thread'] _inherit = ["document.page.history", "mail.thread"]
state = fields.Selection([ state = fields.Selection(
('draft', 'Draft'), [
('to approve', 'Pending Approval'), ("draft", "Draft"),
('approved', 'Approved'), ("to approve", "Pending Approval"),
('cancelled', 'Cancelled')], ("approved", "Approved"),
'Status', ("cancelled", "Cancelled"),
default='draft', ],
"Status",
default="draft",
readonly=True, readonly=True,
) )
approved_date = fields.Datetime( approved_date = fields.Datetime("Approved Date",)
'Approved Date',
)
approved_uid = fields.Many2one( approved_uid = fields.Many2one("res.users", "Approved by",)
'res.users',
'Approved by',
)
is_approval_required = fields.Boolean( is_approval_required = fields.Boolean(
related='page_id.is_approval_required', related="page_id.is_approval_required", string="Approval required",
string="Approval required",
) )
am_i_owner = fields.Boolean( am_i_owner = fields.Boolean(compute="_compute_am_i_owner")
compute='_compute_am_i_owner'
)
am_i_approver = fields.Boolean( am_i_approver = fields.Boolean(related="page_id.am_i_approver", related_sudo=False,)
related='page_id.am_i_approver',
related_sudo=False,
)
page_url = fields.Text( page_url = fields.Text(compute="_compute_page_url", string="URL",)
compute='_compute_page_url',
string="URL",
)
@api.multi @api.multi
def action_draft(self): def action_draft(self):
"""Set a change request as draft""" """Set a change request as draft"""
for rec in self: for rec in self:
if not rec.state == 'cancelled': if not rec.state == "cancelled":
raise UserError( raise UserError(_("You need to cancel it before reopening."))
_('You need to cancel it before reopening.'))
if not (rec.am_i_owner or rec.am_i_approver): if not (rec.am_i_owner or rec.am_i_approver):
raise UserError( raise UserError(
_('You are not authorized to do this.\r\n' _(
'Only owners or approvers can reopen Change Requests.')) "You are not authorized to do this.\r\n"
rec.write({'state': 'draft'}) "Only owners or approvers can reopen Change Requests."
)
)
rec.write({"state": "draft"})
@api.multi @api.multi
def action_to_approve(self): def action_to_approve(self):
"""Set a change request as to approve""" """Set a change request as to approve"""
template = self.env.ref( 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( approver_gid = self.env.ref(
'document_page_approval.group_document_approver_user') "document_page_approval.group_document_approver_user"
)
for rec in self: for rec in self:
if rec.state != 'draft': if rec.state != "draft":
raise UserError( raise UserError(_("Can't approve pages in '%s' state.") % rec.state)
_("Can't approve pages in '%s' state.") % rec.state)
if not (rec.am_i_owner or rec.am_i_approver): if not (rec.am_i_owner or rec.am_i_approver):
raise UserError( 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 # request approval
if rec.is_approval_required: 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] guids = [g.id for g in rec.page_id.approver_group_ids]
users = self.env['res.users'].search([ users = self.env["res.users"].search(
('groups_id', 'in', guids), [("groups_id", "in", guids), ("groups_id", "in", approver_gid.id)]
('groups_id', 'in', approver_gid.id)]) )
rec.message_subscribe([u.id for u in users]) rec.message_subscribe([u.id for u in users])
rec.message_post_with_template(template.id) rec.message_post_with_template(template.id)
else: else:
@ -95,49 +89,49 @@ class DocumentPageHistory(models.Model):
def action_approve(self): def action_approve(self):
"""Set a change request as approved.""" """Set a change request as approved."""
for rec in self: for rec in self:
if rec.state not in ['draft', 'to approve']: if rec.state not in ["draft", "to approve"]:
raise UserError( raise UserError(_("Can't approve page in '%s' state.") % rec.state)
_("Can't approve page in '%s' state.") % rec.state)
if not rec.am_i_approver: if not rec.am_i_approver:
raise UserError(_( raise UserError(
'You are not authorized to do this.\r\n' _(
'Only approvers with these groups can approve this: ' "You are not authorized to do this.\r\n"
) % ', '.join( "Only approvers with these groups can approve this: "
[g.display_name )
for g in rec.page_id.approver_group_ids])) % ", ".join(
[g.display_name for g in rec.page_id.approver_group_ids]
)
)
# Update state # Update state
rec.write({ rec.write(
'state': 'approved', {
'approved_date': fields.datetime.now(), "state": "approved",
'approved_uid': self.env.uid, "approved_date": fields.datetime.now(),
}) "approved_uid": self.env.uid,
}
)
# Trigger computed field update # Trigger computed field update
rec.page_id._compute_history_head() rec.page_id._compute_history_head()
# Notify state change # Notify state change
rec.message_post( rec.message_post(
subtype='mt_comment', subtype="mt_comment",
body=_( body=_("Change request has been approved by %s.")
'Change request has been approved by %s.' % (self.env.user.name),
) % (self.env.user.name)
) )
# Notify followers a new version is available # Notify followers a new version is available
rec.page_id.message_post( rec.page_id.message_post(
subtype='mt_comment', subtype="mt_comment",
body=_( body=_("New version of the document %s approved.") % (rec.page_id.name),
'New version of the document %s approved.'
) % (rec.page_id.name)
) )
@api.multi @api.multi
def action_cancel(self): def action_cancel(self):
"""Set a change request as cancelled.""" """Set a change request as cancelled."""
self.write({'state': 'cancelled'}) self.write({"state": "cancelled"})
for rec in self: for rec in self:
rec.message_post( rec.message_post(
subtype='mt_comment', subtype="mt_comment",
body=_( body=_("Change request <b>%s</b> has been cancelled by %s.")
'Change request <b>%s</b> has been cancelled by %s.' % (rec.display_name, self.env.user.name),
) % (rec.display_name, self.env.user.name)
) )
@api.multi @api.multi
@ -150,36 +144,31 @@ class DocumentPageHistory(models.Model):
def _compute_am_i_owner(self): def _compute_am_i_owner(self):
"""Check if current user is the owner""" """Check if current user is the owner"""
for rec in self: 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 @api.multi
def _compute_page_url(self): def _compute_page_url(self):
"""Compute the page url.""" """Compute the page url."""
for page in self: for page in self:
base_url = self.env['ir.config_parameter'].sudo().get_param( base_url = (
'web.base.url', self.env["ir.config_parameter"]
default='http://localhost:8069' .sudo()
.get_param("web.base.url", default="http://localhost:8069")
) )
page.page_url = ( page.page_url = (
'{}/web#db={}&id={}&view_type=form&' "{}/web#db={}&id={}&view_type=form&" "model=document.page.history"
'model=document.page.history').format( ).format(base_url, self.env.cr.dbname, page.id)
base_url,
self.env.cr.dbname,
page.id
)
@api.multi @api.multi
def _compute_diff(self): def _compute_diff(self):
"""Shows a diff between this version and the previous version""" """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: for rec in self:
domain = [ domain = [("page_id", "=", rec.page_id.id), ("state", "=", "approved")]
('page_id', '=', rec.page_id.id),
('state', '=', 'approved')]
if rec.approved_date: if rec.approved_date:
domain.append(('approved_date', '<', rec.approved_date)) domain.append(("approved_date", "<", rec.approved_date))
prev = history.search(domain, limit=1, order='approved_date DESC') prev = history.search(domain, limit=1, order="approved_date DESC")
if prev: if prev:
rec.diff = self._get_diff(prev.id, rec.id) rec.diff = self._get_diff(prev.id, rec.id)
else: else:

View File

@ -1,36 +1,36 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="group_document_approver_user" model="res.groups"> <record id="group_document_approver_user" model="res.groups">
<field name="name">Approver</field> <field name="name">Approver</field>
<field name="category_id" ref="knowledge.module_category_knowledge"/> <field name="category_id" ref="knowledge.module_category_knowledge" />
<field name="implied_ids" eval="[(4, ref('document_page.group_document_editor'))]"/> <field
name="implied_ids"
eval="[(4, ref('document_page.group_document_editor'))]"
/>
</record> </record>
<record id="document_page.group_document_manager" model="res.groups"> <record id="document_page.group_document_manager" model="res.groups">
<field name="implied_ids" eval="[(4, ref('group_document_approver_user'))]"/> <field name="implied_ids" eval="[(4, ref('group_document_approver_user'))]" />
</record> </record>
<record model="ir.rule" id="rule_change_request_global"> <record model="ir.rule" id="rule_change_request_global">
<field name="name">Change Request Global</field> <field name="name">Change Request Global</field>
<field name="model_id" ref="model_document_page_history"/> <field name="model_id" ref="model_document_page_history" />
<field name="groups" eval="[(6, 0, [ref('knowledge.group_document_user')])]"/> <field name="groups" eval="[(6, 0, [ref('knowledge.group_document_user')])]" />
<field name="domain_force">['|',('state','=','approved'),('create_uid','=',user.id)]</field> <field
<field name="perm_read" eval="True"/> name="domain_force"
<field name="perm_write" eval="True"/> >['|',('state','=','approved'),('create_uid','=',user.id)]</field>
<field name="perm_unlink" eval="True"/> <field name="perm_read" eval="True" />
<field name="perm_create" eval="True"/> <field name="perm_write" eval="True" />
<field name="perm_unlink" eval="True" />
<field name="perm_create" eval="True" />
</record> </record>
<record model="ir.rule" id="rule_change_request_approver"> <record model="ir.rule" id="rule_change_request_approver">
<field name="name">Change Request Approver</field> <field name="name">Change Request Approver</field>
<field name="model_id" ref="model_document_page_history"/> <field name="model_id" ref="model_document_page_history" />
<field name="groups" eval="[(6, 0, [ref('group_document_approver_user')])]"/> <field name="groups" eval="[(6, 0, [ref('group_document_approver_user')])]" />
<field name="domain_force">[('state','!=','draft')]</field> <field name="domain_force">[('state','!=','draft')]</field>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True" />
<field name="perm_write" eval="True"/> <field name="perm_write" eval="True" />
<field name="perm_unlink" eval="True"/> <field name="perm_unlink" eval="True" />
<field name="perm_create" eval="True"/> <field name="perm_create" eval="True" />
</record> </record>
</odoo> </odoo>

View File

@ -2,31 +2,35 @@ from odoo.tests import common
class TestDocumentPageApproval(common.TransactionCase): class TestDocumentPageApproval(common.TransactionCase):
def setUp(self): def setUp(self):
super(TestDocumentPageApproval, self).setUp() super(TestDocumentPageApproval, self).setUp()
self.page_obj = self.env['document.page'] self.page_obj = self.env["document.page"]
self.history_obj = self.env['document.page.history'] self.history_obj = self.env["document.page.history"]
# demo # demo
self.category1 = self.env.ref('document_page.demo_category1') self.category1 = self.env.ref("document_page.demo_category1")
self.page1 = self.env.ref('document_page.demo_page1') self.page1 = self.env.ref("document_page.demo_page1")
self.approver_gid = self.env.ref( self.approver_gid = self.env.ref(
'document_page_approval.group_document_approver_user') "document_page_approval.group_document_approver_user"
self.env.ref('base.user_root').write({ )
'groups_id': [(4, self.approver_gid.id)], self.env.ref("base.user_root").write(
}) {"groups_id": [(4, self.approver_gid.id)],}
)
# demo_approval # demo_approval
self.category2 = self.page_obj.create({ self.category2 = self.page_obj.create(
'name': 'This category requires approval', {
'type': 'category', "name": "This category requires approval",
'approval_required': True, "type": "category",
'approver_gid': self.approver_gid.id, "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, self.page2 = self.page_obj.create(
'content': 'This content will require approval', {
}) "name": "This page requires approval",
"parent_id": self.category2.id,
"content": "This content will require approval",
}
)
def test_approval_required(self): def test_approval_required(self):
page = self.page2 page = self.page2
@ -36,13 +40,12 @@ class TestDocumentPageApproval(common.TransactionCase):
def test_change_request_approve(self): def test_change_request_approve(self):
page = self.page2 page = self.page2
chreq = self.history_obj.search([ chreq = self.history_obj.search(
('page_id', '=', page.id), [("page_id", "=", page.id), ("state", "!=", "approved")]
('state', '!=', 'approved') )[0]
])[0]
# It should automatically be in 'to approve' state # 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) self.assertNotEqual(chreq.content, page.content)
# who_am_i # who_am_i
@ -51,66 +54,66 @@ class TestDocumentPageApproval(common.TransactionCase):
# approve # approve
chreq.action_approve() chreq.action_approve()
self.assertEqual(chreq.state, 'approved') self.assertEqual(chreq.state, "approved")
self.assertEqual(chreq.content, page.content) self.assertEqual(chreq.content, page.content)
# new changes should create change requests # new changes should create change requests
page.write({'content': 'New content'}) page.write({"content": "New content"})
self.assertNotEqual(page.content, 'New content') self.assertNotEqual(page.content, "New content")
chreq = self.history_obj.search([ chreq = self.history_obj.search(
('page_id', '=', page.id), [("page_id", "=", page.id), ("state", "!=", "approved")]
('state', '!=', 'approved') )[0]
])[0]
chreq.action_approve() chreq.action_approve()
self.assertEqual(page.content, 'New content') self.assertEqual(page.content, "New content")
def test_change_request_auto_approve(self): def test_change_request_auto_approve(self):
page = self.page1 page = self.page1
self.assertFalse(page.is_approval_required) self.assertFalse(page.is_approval_required)
page.write({'content': 'New content'}) page.write({"content": "New content"})
self.assertEqual(page.content, 'New content') self.assertEqual(page.content, "New content")
def test_change_request_from_scratch(self): def test_change_request_from_scratch(self):
page = self.page2 page = self.page2
# aprove everything # aprove everything
self.history_obj.search([ self.history_obj.search(
('page_id', '=', page.id), [("page_id", "=", page.id), ("state", "!=", "approved")]
('state', '!=', 'approved') ).action_approve()
]).action_approve()
# new change request from scrath # new change request from scrath
chreq = self.history_obj.create({ chreq = self.history_obj.create(
'page_id': page.id, {
'summary': 'Changed something', "page_id": page.id,
'content': 'New content', "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.content, chreq.content)
self.assertNotEqual(page.approved_date, chreq.approved_date) self.assertNotEqual(page.approved_date, chreq.approved_date)
self.assertNotEqual(page.approved_uid, chreq.approved_uid) self.assertNotEqual(page.approved_uid, chreq.approved_uid)
chreq.action_to_approve() 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.content, chreq.content)
self.assertNotEqual(page.approved_date, chreq.approved_date) self.assertNotEqual(page.approved_date, chreq.approved_date)
self.assertNotEqual(page.approved_uid, chreq.approved_uid) self.assertNotEqual(page.approved_uid, chreq.approved_uid)
chreq.action_cancel() chreq.action_cancel()
self.assertEqual(chreq.state, 'cancelled') self.assertEqual(chreq.state, "cancelled")
self.assertNotEqual(page.content, chreq.content) self.assertNotEqual(page.content, chreq.content)
self.assertNotEqual(page.approved_date, chreq.approved_date) self.assertNotEqual(page.approved_date, chreq.approved_date)
self.assertNotEqual(page.approved_uid, chreq.approved_uid) self.assertNotEqual(page.approved_uid, chreq.approved_uid)
chreq.action_draft() chreq.action_draft()
self.assertEqual(chreq.state, 'draft') self.assertEqual(chreq.state, "draft")
self.assertNotEqual(page.content, chreq.content) self.assertNotEqual(page.content, chreq.content)
self.assertNotEqual(page.approved_date, chreq.approved_date) self.assertNotEqual(page.approved_date, chreq.approved_date)
self.assertNotEqual(page.approved_uid, chreq.approved_uid) self.assertNotEqual(page.approved_uid, chreq.approved_uid)
chreq.action_approve() chreq.action_approve()
self.assertEqual(chreq.state, 'approved') self.assertEqual(chreq.state, "approved")
self.assertEqual(page.content, chreq.content) self.assertEqual(page.content, chreq.content)
self.assertEqual(page.approved_date, chreq.approved_date) self.assertEqual(page.approved_date, chreq.approved_date)
self.assertEqual(page.approved_uid, chreq.approved_uid) self.assertEqual(page.approved_uid, chreq.approved_uid)
@ -122,6 +125,6 @@ class TestDocumentPageApproval(common.TransactionCase):
def test_get_page_url(self): def test_get_page_url(self):
"""Test if page url exist.""" """Test if page url exist."""
pages = self.env['document.page.history'].search([]) pages = self.env["document.page.history"].search([])
page = pages[0] page = pages[0]
self.assertIsNotNone(page.page_url) self.assertIsNotNone(page.page_url)

View File

@ -1,124 +1,196 @@
<?xml version="1.0"?> <?xml version="1.0" ?>
<odoo> <odoo>
<!-- History Form View --> <!-- History Form View -->
<record id="wiki_history_form_inherit" model="ir.ui.view"> <record id="wiki_history_form_inherit" model="ir.ui.view">
<field name="name">document.page.history.form</field> <field name="name">document.page.history.form</field>
<field name="model">document.page.history</field> <field name="model">document.page.history</field>
<field name="inherit_id" ref="document_page.wiki_history_form"/> <field name="inherit_id" ref="document_page.wiki_history_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<sheet position="before"> <sheet position="before">
<header> <header>
<!-- draft -> to approve --> <!-- draft -> to approve -->
<button name="action_to_approve" type="object" string="Send to Review" class="oe_highlight" <button
attrs="{'invisible':['|','|',('is_approval_required','=',False),('am_i_owner','=',False),('state', 'not in', ['draft'])]}"/> name="action_to_approve"
type="object"
string="Send to Review"
class="oe_highlight"
attrs="{'invisible':['|','|',('is_approval_required','=',False),('am_i_owner','=',False),('state', 'not in', ['draft'])]}"
/>
<!-- approve if i am approver --> <!-- approve if i am approver -->
<button name="action_approve" type="object" string="Approve" class="oe_highlight" <button
attrs="{'invisible':['|','|',('is_approval_required','=',False),('am_i_approver','=',False),('state','not in',['draft','to approve'])]}"/> name="action_approve"
type="object"
string="Approve"
class="oe_highlight"
attrs="{'invisible':['|','|',('is_approval_required','=',False),('am_i_approver','=',False),('state','not in',['draft','to approve'])]}"
/>
<!-- approve if it's not required and i am owner --> <!-- approve if it's not required and i am owner -->
<button name="action_approve" type="object" string="Approve" class="oe_highlight" <button
attrs="{'invisible':['|','|',('is_approval_required','=',True),('am_i_owner','=',False),('state','not in',['draft', 'to approve'])]}"/> name="action_approve"
type="object"
string="Approve"
class="oe_highlight"
attrs="{'invisible':['|','|',('is_approval_required','=',True),('am_i_owner','=',False),('state','not in',['draft', 'to approve'])]}"
/>
<!-- cancel if i am owner or approver --> <!-- cancel if i am owner or approver -->
<button name="action_cancel" type="object" string="Cancel" <button
attrs="{'invisible':['|','&amp;',('am_i_owner','=',False),('am_i_approver','=',False),('state','not in',['draft','to approve'])]}"/> name="action_cancel"
type="object"
string="Cancel"
attrs="{'invisible':['|','&amp;',('am_i_owner','=',False),('am_i_approver','=',False),('state','not in',['draft','to approve'])]}"
/>
<!-- reopen if i am owner or approver --> <!-- reopen if i am owner or approver -->
<button name="action_draft" type="object" string="Back to draft" <button
attrs="{'invisible':['|','&amp;',('am_i_owner','=',False),('am_i_approver','=',False),('state','not in',['cancelled'])]}"/> name="action_draft"
type="object"
string="Back to draft"
attrs="{'invisible':['|','&amp;',('am_i_owner','=',False),('am_i_approver','=',False),('state','not in',['cancelled'])]}"
/>
<!-- cancel & reopen, if i am owner or approver --> <!-- cancel & reopen, if i am owner or approver -->
<button name="action_draft" type="object" string="Back to draft" <button
attrs="{'invisible':['|','&amp;',('am_i_owner','=',False),('am_i_approver','=',False),('state','not in',['to approve'])]}"/> name="action_draft"
<field name="am_i_owner" invisible="1"/> type="object"
<field name="am_i_approver" invisible="1"/> string="Back to draft"
<field name="is_approval_required" invisible="1"/> attrs="{'invisible':['|','&amp;',('am_i_owner','=',False),('am_i_approver','=',False),('state','not in',['to approve'])]}"
<field name="state" widget="statusbar" statusbar_visible="draft,approved" /> />
<field name="am_i_owner" invisible="1" />
<field name="am_i_approver" invisible="1" />
<field name="is_approval_required" invisible="1" />
<field
name="state"
widget="statusbar"
statusbar_visible="draft,approved"
/>
</header> </header>
</sheet> </sheet>
<xpath expr="//field[@name='create_uid']/parent::group" position="after"> <xpath expr="//field[@name='create_uid']/parent::group" position="after">
<group> <group>
<field name="approved_uid" readonly="1" attrs="{'invisible':[('state','not in',['approved'])]}"/> <field
<field name="approved_date" readonly="1" attrs="{'invisible':[('state','not in',['approved'])]}"/> name="approved_uid"
readonly="1"
attrs="{'invisible':[('state','not in',['approved'])]}"
/>
<field
name="approved_date"
readonly="1"
attrs="{'invisible':[('state','not in',['approved'])]}"
/>
</group> </group>
</xpath> </xpath>
<!-- Readonly fields --> <!-- Readonly fields -->
<field name="content" position="attributes"><attribute name="attrs">{'readonly': [('state', 'not in', ['draft'])]}</attribute></field> <field name="content" position="attributes">
<field name="page_id" position="attributes"><attribute name="attrs">{'readonly': [('state', 'not in', ['draft'])]}</attribute></field> <attribute
<field name="name" position="attributes"><attribute name="attrs">{'readonly': [('state', 'not in', ['draft'])]}</attribute></field> name="attrs"
<field name="summary" position="attributes"><attribute name="attrs">{'readonly': [('state', 'not in', ['draft'])]}</attribute></field> >{'readonly': [('state', 'not in', ['draft'])]}</attribute>
</field>
<field name="page_id" position="attributes">
<attribute
name="attrs"
>{'readonly': [('state', 'not in', ['draft'])]}</attribute>
</field>
<field name="name" position="attributes">
<attribute
name="attrs"
>{'readonly': [('state', 'not in', ['draft'])]}</attribute>
</field>
<field name="summary" position="attributes">
<attribute
name="attrs"
>{'readonly': [('state', 'not in', ['draft'])]}</attribute>
</field>
<!-- Chatter --> <!-- Chatter -->
<sheet position="after"> <sheet position="after">
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers" />
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread" />
</div> </div>
</sheet> </sheet>
</field> </field>
</record> </record>
<!-- History Form View Manager Rights --> <!-- History Form View Manager Rights -->
<record id="wiki_history_form_inherit_manager" model="ir.ui.view"> <record id="wiki_history_form_inherit_manager" model="ir.ui.view">
<field name="name">document.page.history.form</field> <field name="name">document.page.history.form</field>
<field name="model">document.page.history</field> <field name="model">document.page.history</field>
<field name="inherit_id" ref="wiki_history_form_inherit"/> <field name="inherit_id" ref="wiki_history_form_inherit" />
<field name="groups_id" eval="[(6, 0, [ref('document_page.group_document_manager')])]"/> <field
name="groups_id"
eval="[(6, 0, [ref('document_page.group_document_manager')])]"
/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<!-- Readonly fields --> <!-- Readonly fields -->
<field name="name" position="attributes"><attribute name="attrs">{'readonly': False}</attribute></field> <field name="name" position="attributes">
<field name="summary" position="attributes"><attribute name="attrs">{'readonly': False}</attribute></field> <attribute name="attrs">{'readonly': False}</attribute>
</field>
<field name="summary" position="attributes">
<attribute name="attrs">{'readonly': False}</attribute>
</field>
</field> </field>
</record> </record>
<!-- Page Form View --> <!-- Page Form View -->
<record id="wiki_form_inherit" model="ir.ui.view"> <record id="wiki_form_inherit" model="ir.ui.view">
<field name="name">document.page.form</field> <field name="name">document.page.form</field>
<field name="model">document.page</field> <field name="model">document.page</field>
<field name="inherit_id" ref="document_page.view_wiki_form" /> <field name="inherit_id" ref="document_page.view_wiki_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<sheet position="before"> <sheet position="before">
<div class="alert alert-info" role="alert" style="margin-bottom:0px;" <div
attrs="{'invisible': [('has_changes_pending_approval','=',False)]}"> class="alert alert-info"
This document has <b>Changes Pending Approval</b>. You are viewing the last approved content. role="alert"
style="margin-bottom:0px;"
attrs="{'invisible': [('has_changes_pending_approval','=',False)]}"
>
This document has <b
>Changes Pending Approval</b>. You are viewing the last approved content.
</div> </div>
<div class="alert alert-warning oe_edit_only" role="alert" style="margin-bottom:0px;" <div
attrs="{'invisible': [('is_approval_required','=',False)]}"> class="alert alert-warning oe_edit_only"
This document requires approval. If edited, you will create a new <b>Change Request</b>. role="alert"
style="margin-bottom:0px;"
attrs="{'invisible': [('is_approval_required','=',False)]}"
>
This document requires approval. If edited, you will create a new <b
>Change Request</b>.
</div> </div>
<div class="alert alert-warning oe_edit_only" role="alert" style="margin-bottom:0px;" <div
attrs="{'invisible': [('user_has_drafts','=',False)]}"> class="alert alert-warning oe_edit_only"
role="alert"
style="margin-bottom:0px;"
attrs="{'invisible': [('user_has_drafts','=',False)]}"
>
<b>You already have a Draft Change Request for this page.</b> <b>You already have a Draft Change Request for this page.</b>
It is highly recommended that you edit that one instead of creating a new one. It is highly recommended that you edit that one instead of creating a new one.
</div> </div>
<field name="is_approval_required" invisible="1"/> <field name="is_approval_required" invisible="1" />
<field name="has_changes_pending_approval" invisible="1"/> <field name="has_changes_pending_approval" invisible="1" />
<field name="user_has_drafts" invisible="1"/> <field name="user_has_drafts" invisible="1" />
</sheet> </sheet>
<button name="toggle_active" position="after"> <button name="toggle_active" position="after">
<button class="oe_stat_button" name="action_changes_pending_approval" string="Change Requests" type="object" <button
attrs="{'invisible':[('has_changes_pending_approval','=',False),('user_has_drafts','=',False)]}" icon="fa-edit"/> class="oe_stat_button"
name="action_changes_pending_approval"
string="Change Requests"
type="object"
attrs="{'invisible':[('has_changes_pending_approval','=',False),('user_has_drafts','=',False)]}"
icon="fa-edit"
/>
</button> </button>
<field name="content_uid" position="after"> <field name="content_uid" position="after">
<field name="approved_uid"/> <field name="approved_uid" />
</field> </field>
<field name="content_date" position="replace"> <field name="content_date" position="replace">
<field name="approved_date"/> <field name="approved_date" />
</field> </field>
<field name="history_ids" position="inside"> <field name="history_ids" position="inside">
<tree> <tree>
<field name="id"/> <field name="id" />
<field name="approved_date"/> <field name="approved_date" />
<field name="summary"/> <field name="summary" />
<field name="create_uid"/> <field name="create_uid" />
<field name="approved_uid"/> <field name="approved_uid" />
</tree> </tree>
</field> </field>
</field> </field>
</record> </record>
<!-- Page Menu Form View --> <!-- Page Menu Form View -->
<record id="view_wiki_menu_form_inherit" model="ir.ui.view"> <record id="view_wiki_menu_form_inherit" model="ir.ui.view">
<field name="name">document.page.form</field> <field name="name">document.page.form</field>
@ -126,14 +198,16 @@
<field name="inherit_id" ref="document_page.view_wiki_menu_form" /> <field name="inherit_id" ref="document_page.view_wiki_menu_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="content" position="before"> <field name="content" position="before">
<group class="oe_read_only" attrs="{'invisible':[('type','!=','content')]}"> <group
class="oe_read_only"
attrs="{'invisible':[('type','!=','content')]}"
>
<field name="approved_date" /> <field name="approved_date" />
<field name="approved_uid" /> <field name="approved_uid" />
</group> </group>
</field> </field>
</field> </field>
</record> </record>
<!-- Catgory Form View --> <!-- Catgory Form View -->
<record id="view_category_form_inherit" model="ir.ui.view"> <record id="view_category_form_inherit" model="ir.ui.view">
<field name="name">document.page.category.form</field> <field name="name">document.page.category.form</field>
@ -141,18 +215,19 @@
<field name="inherit_id" ref="document_page.view_category_form" /> <field name="inherit_id" ref="document_page.view_category_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="parent_id" position="after"> <field name="parent_id" position="after">
<field name="approval_required"/> <field name="approval_required" />
<field name="approver_gid" <field
attrs="{'invisible':[('approval_required','!=', True)], 'required':[('approval_required','=', True)]}"/> name="approver_gid"
attrs="{'invisible':[('approval_required','!=', True)], 'required':[('approval_required','=', True)]}"
/>
</field> </field>
</field> </field>
</record> </record>
<!-- History Tree view --> <!-- History Tree view -->
<record id="view_wiki_history_tree_inherit" model="ir.ui.view"> <record id="view_wiki_history_tree_inherit" model="ir.ui.view">
<field name="name">document.page.history.tree</field> <field name="name">document.page.history.tree</field>
<field name="model">document.page.history</field> <field name="model">document.page.history</field>
<field name="inherit_id" ref="document_page.view_wiki_history_tree"/> <field name="inherit_id" ref="document_page.view_wiki_history_tree" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree position="attributes"> <tree position="attributes">
<attribute name="decoration-info">state=='draft'</attribute> <attribute name="decoration-info">state=='draft'</attribute>
@ -160,52 +235,71 @@
<attribute name="decoration-muted">state=='cancelled'</attribute> <attribute name="decoration-muted">state=='cancelled'</attribute>
</tree> </tree>
<tree position="inside"> <tree position="inside">
<field name="state"/> <field name="state" />
<field name="approved_uid"/> <field name="approved_uid" />
<field name="approved_date"/> <field name="approved_date" />
</tree> </tree>
</field> </field>
</record> </record>
<!-- History Search view --> <!-- History Search view -->
<record id="view_wiki_history_filter" model="ir.ui.view"> <record id="view_wiki_history_filter" model="ir.ui.view">
<field name="name">document.page.history.search</field> <field name="name">document.page.history.search</field>
<field name="model">document.page.history</field> <field name="model">document.page.history</field>
<field name="inherit_id" ref="document_page.view_wiki_history_filter"/> <field name="inherit_id" ref="document_page.view_wiki_history_filter" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="page_id" position="before"> <field name="page_id" position="before">
<field name="state"/> <field name="state" />
</field> </field>
<field name="create_uid" position="after"> <field name="create_uid" position="after">
<filter name="draft" string="Draft" domain="[('state','=','draft')]"/> <filter name="draft" string="Draft" domain="[('state','=','draft')]" />
<filter name="pending" string="Pending Approval" domain="[('state','=','to approve')]"/> <filter
<filter name="approved" string="Approved" domain="[('state','=','approved')]"/> name="pending"
<filter name="cancelled" string="Cancelled" domain="[('state','=','cancelled')]"/> string="Pending Approval"
domain="[('state','=','to approve')]"
/>
<filter
name="approved"
string="Approved"
domain="[('state','=','approved')]"
/>
<filter
name="cancelled"
string="Cancelled"
domain="[('state','=','cancelled')]"
/>
</field> </field>
<filter name="group_by_author" position="before"> <filter name="group_by_author" position="before">
<filter name="group_state" string="State" context="{'group_by':'state'}" /> <filter
name="group_state"
string="State"
context="{'group_by':'state'}"
/>
</filter> </filter>
<filter name="group_by_author" position="after"> <filter name="group_by_author" position="after">
<filter name="group_approver" string="Approver" context="{'group_by':'approved_uid'}" /> <filter
name="group_approver"
string="Approver"
context="{'group_by':'approved_uid'}"
/>
</filter> </filter>
</field> </field>
</record> </record>
<!-- Change Requests Action --> <!-- Change Requests Action -->
<record model="ir.actions.act_window" id="action_change_requests"> <record model="ir.actions.act_window" id="action_change_requests">
<field name="name">Change Requests</field> <field name="name">Change Requests</field>
<field name="res_model">document.page.history</field> <field name="res_model">document.page.history</field>
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="context">{'search_default_draft': 1, 'search_default_pending': 1}</field> <field
name="context"
>{'search_default_draft': 1, 'search_default_pending': 1}</field>
</record> </record>
<menuitem
<menuitem id="menu_page_change_requests" id="menu_page_change_requests"
name="Change Requests" name="Change Requests"
parent="document_page.menu_wiki" parent="document_page.menu_wiki"
action="action_change_requests" action="action_change_requests"
sequence="25" sequence="25"
groups="document_page.group_document_editor" /> groups="document_page.group_document_editor"
/>
</odoo> </odoo>