[IMP] base_exception: black, isort

pull/2463/head
Jared Kipe 2020-03-13 09:16:47 -07:00 committed by matiasperalta1
parent 5a4d2aefc0
commit 1100cd5dbc
10 changed files with 192 additions and 183 deletions

View File

@ -3,24 +3,21 @@
# Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com> # Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{ {
'name': 'Exception Rule', "name": "Exception Rule",
'version': '12.0.3.0.1', "version": "12.0.3.0.1",
'category': 'Generic Modules', "category": "Generic Modules",
'summary': """ "summary": """
This module provide an abstract model to manage customizable This module provide an abstract model to manage customizable
exceptions to be applied on different models (sale order, invoice, ...)""", exceptions to be applied on different models (sale order, invoice, ...)""",
'author': "author": "Akretion, Sodexis, Camptocamp, Odoo Community Association (OCA)",
"Akretion, Sodexis, Camptocamp, Odoo Community Association (OCA)", "website": "https://github.com/OCA/server-tools",
'website': 'https://github.com/OCA/server-tools', "depends": ["base_setup"],
'depends': [ "license": "AGPL-3",
'base_setup', "data": [
"security/base_exception_security.xml",
"security/ir.model.access.csv",
"wizard/base_exception_confirm_view.xml",
"views/base_exception_view.xml",
], ],
'license': 'AGPL-3', "installable": True,
'data': [
'security/base_exception_security.xml',
'security/ir.model.access.csv',
'wizard/base_exception_confirm_view.xml',
'views/base_exception_view.xml',
],
'installable': True,
} }

View File

@ -1,2 +1 @@
from . import base_exception from . import base_exception

View File

@ -3,52 +3,56 @@
# Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com> # Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import time
import html import html
from odoo import api, fields, models, _ import time
from odoo import _, api, fields, models, osv
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
from odoo.tools.safe_eval import safe_eval from odoo.tools.safe_eval import safe_eval
from odoo import osv
class ExceptionRule(models.Model): class ExceptionRule(models.Model):
_name = 'exception.rule' _name = "exception.rule"
_description = 'Exception Rule' _description = "Exception Rule"
_order = 'active desc, sequence asc' _order = "active desc, sequence asc"
name = fields.Char('Exception Name', required=True, translate=True) name = fields.Char("Exception Name", required=True, translate=True)
description = fields.Text('Description', translate=True) description = fields.Text("Description", translate=True)
sequence = fields.Integer( sequence = fields.Integer(
string='Sequence', string="Sequence", help="Gives the sequence order when applying the test"
help="Gives the sequence order when applying the test",
) )
model = fields.Selection(selection=[], string='Apply on', required=True) model = fields.Selection(selection=[], string="Apply on", required=True)
exception_type = fields.Selection( exception_type = fields.Selection(
selection=[('by_domain', 'By domain'), selection=[("by_domain", "By domain"), ("by_py_code", "By python code")],
('by_py_code', 'By python code')], string="Exception Type",
string='Exception Type', required=True, default='by_py_code', required=True,
default="by_py_code",
help="By python code: allow to define any arbitrary check\n" help="By python code: allow to define any arbitrary check\n"
"By domain: limited to a selection by an odoo domain:\n" "By domain: limited to a selection by an odoo domain:\n"
" performance can be better when exceptions " " performance can be better when exceptions "
" are evaluated with several records") " are evaluated with several records",
domain = fields.Char('Domain') )
domain = fields.Char("Domain")
active = fields.Boolean('Active', default=True) active = fields.Boolean("Active", default=True)
code = fields.Text( code = fields.Text(
'Python Code', "Python Code",
help="Python code executed to check if the exception apply or " help="Python code executed to check if the exception apply or "
"not. Use failed = True to block the exception", "not. Use failed = True to block the exception",
) )
@api.constrains('exception_type', 'domain', 'code') @api.constrains("exception_type", "domain", "code")
def check_exception_type_consistency(self): def check_exception_type_consistency(self):
for rule in self: for rule in self:
if ((rule.exception_type == 'by_py_code' and not rule.code) or if (rule.exception_type == "by_py_code" and not rule.code) or (
(rule.exception_type == 'by_domain' and not rule.domain)): rule.exception_type == "by_domain" and not rule.domain
):
raise ValidationError( raise ValidationError(
_("There is a problem of configuration, python code or " _(
"domain is missing to match the exception type.") "There is a problem of configuration, python code or "
"domain is missing to match the exception type."
)
) )
@api.multi @api.multi
@ -59,8 +63,8 @@ class ExceptionRule(models.Model):
class BaseExceptionMethod(models.AbstractModel): class BaseExceptionMethod(models.AbstractModel):
_name = 'base.exception.method' _name = "base.exception.method"
_description = 'Exception Rule Methods' _description = "Exception Rule Methods"
@api.multi @api.multi
def _get_main_records(self): def _get_main_records(self):
@ -81,15 +85,14 @@ class BaseExceptionMethod(models.AbstractModel):
By default, only the rules with the correct model By default, only the rules with the correct model
will be used. will be used.
""" """
return [('model', '=', self._name)] return [("model", "=", self._name)]
@api.multi @api.multi
def detect_exceptions(self): def detect_exceptions(self):
"""List all exception_ids applied on self """List all exception_ids applied on self
Exception ids are also written on records Exception ids are also written on records
""" """
rules = self.env['exception.rule'].sudo().search( rules = self.env["exception.rule"].sudo().search(self._rule_domain())
self._rule_domain())
all_exception_ids = [] all_exception_ids = []
rules_to_remove = {} rules_to_remove = {}
rules_to_add = {} rules_to_add = {}
@ -123,23 +126,23 @@ class BaseExceptionMethod(models.AbstractModel):
# table # table
# and the "to add" part generates one INSERT (with unnest) per rule. # and the "to add" part generates one INSERT (with unnest) per rule.
for rule_id, records in rules_to_remove.items(): for rule_id, records in rules_to_remove.items():
records.write({'exception_ids': [(3, rule_id,)]}) records.write({"exception_ids": [(3, rule_id)]})
for rule_id, records in rules_to_add.items(): for rule_id, records in rules_to_add.items():
records.write(({'exception_ids': [(4, rule_id,)]})) records.write({"exception_ids": [(4, rule_id)]})
return all_exception_ids return all_exception_ids
@api.model @api.model
def _exception_rule_eval_context(self, rec): def _exception_rule_eval_context(self, rec):
return { return {
'time': time, "time": time,
'self': rec, "self": rec,
# object, obj: deprecated. # object, obj: deprecated.
# should be removed in future migrations # should be removed in future migrations
'object': rec, "object": rec,
'obj': rec, "obj": rec,
# copy context to prevent side-effects of eval # copy context to prevent side-effects of eval
# should be deprecated too, accesible through self. # should be deprecated too, accesible through self.
'context': self.env.context.copy() "context": self.env.context.copy(),
} }
@api.model @api.model
@ -147,26 +150,26 @@ class BaseExceptionMethod(models.AbstractModel):
expr = rule.code expr = rule.code
space = self._exception_rule_eval_context(rec) space = self._exception_rule_eval_context(rec)
try: try:
safe_eval(expr, safe_eval(
space, expr, space, mode="exec", nocopy=True
mode='exec', ) # nocopy allows to return 'result'
nocopy=True) # nocopy allows to return 'result'
except Exception as e: except Exception as e:
raise UserError( raise UserError(
_('Error when evaluating the exception.rule ' _("Error when evaluating the exception.rule " "rule:\n %s \n(%s)")
'rule:\n %s \n(%s)') % (rule.name, e)) % (rule.name, e)
return space.get('failed', False) )
return space.get("failed", False)
@api.multi @api.multi
def _detect_exceptions(self, rule): def _detect_exceptions(self, rule):
if rule.exception_type == 'by_py_code': if rule.exception_type == "by_py_code":
return self._detect_exceptions_by_py_code(rule) return self._detect_exceptions_by_py_code(rule)
elif rule.exception_type == 'by_domain': elif rule.exception_type == "by_domain":
return self._detect_exceptions_by_domain(rule) return self._detect_exceptions_by_domain(rule)
@api.multi @api.multi
def _get_base_domain(self): def _get_base_domain(self):
return [('ignore_exception', '=', False), ('id', 'in', self.ids)] return [("ignore_exception", "=", False), ("id", "in", self.ids)]
@api.multi @api.multi
def _detect_exceptions_by_py_code(self, rule): def _detect_exceptions_by_py_code(self, rule):
@ -193,34 +196,29 @@ class BaseExceptionMethod(models.AbstractModel):
class BaseException(models.AbstractModel): class BaseException(models.AbstractModel):
_inherit = 'base.exception.method' _inherit = "base.exception.method"
_name = 'base.exception' _name = "base.exception"
_order = 'main_exception_id asc' _order = "main_exception_id asc"
_description = 'Exception' _description = "Exception"
main_exception_id = fields.Many2one( main_exception_id = fields.Many2one(
'exception.rule', "exception.rule",
compute='_compute_main_error', compute="_compute_main_error",
string='Main Exception', string="Main Exception",
store=True, store=True,
) )
exceptions_summary = fields.Html( exceptions_summary = fields.Html(
'Exceptions Summary', "Exceptions Summary", compute="_compute_exceptions_summary"
compute='_compute_exceptions_summary',
) )
exception_ids = fields.Many2many( exception_ids = fields.Many2many("exception.rule", string="Exceptions", copy=False)
'exception.rule', ignore_exception = fields.Boolean("Ignore Exceptions", copy=False)
string='Exceptions',
copy=False,
)
ignore_exception = fields.Boolean('Ignore Exceptions', copy=False)
@api.multi @api.multi
def action_ignore_exceptions(self): def action_ignore_exceptions(self):
self.write({'ignore_exception': True}) self.write({"ignore_exception": True})
return True return True
@api.depends('exception_ids', 'ignore_exception') @api.depends("exception_ids", "ignore_exception")
def _compute_main_error(self): def _compute_main_error(self):
for rec in self: for rec in self:
if not rec.ignore_exception and rec.exception_ids: if not rec.ignore_exception and rec.exception_ids:
@ -228,29 +226,35 @@ class BaseException(models.AbstractModel):
else: else:
rec.main_exception_id = False rec.main_exception_id = False
@api.depends('exception_ids', 'ignore_exception') @api.depends("exception_ids", "ignore_exception")
def _compute_exceptions_summary(self): def _compute_exceptions_summary(self):
for rec in self: for rec in self:
if rec.exception_ids and not rec.ignore_exception: if rec.exception_ids and not rec.ignore_exception:
rec.exceptions_summary = '<ul>%s</ul>' % ''.join([ rec.exceptions_summary = "<ul>%s</ul>" % "".join(
'<li>%s: <i>%s</i></li>' % tuple(map(html.escape, ( [
e.name, e.description))) for e in rec.exception_ids]) "<li>%s: <i>%s</i></li>"
% tuple(map(html.escape, (e.name, e.description)))
for e in rec.exception_ids
]
)
@api.multi @api.multi
def _popup_exceptions(self): def _popup_exceptions(self):
action = self._get_popup_action().read()[0] action = self._get_popup_action().read()[0]
action.update({ action.update(
'context': { {
'active_id': self.ids[0], "context": {
'active_ids': self.ids, "active_id": self.ids[0],
'active_model': self._name, "active_ids": self.ids,
"active_model": self._name,
}
} }
}) )
return action return action
@api.model @api.model
def _get_popup_action(self): def _get_popup_action(self):
return self.env.ref('base_exception.action_exception_rule_confirm') return self.env.ref("base_exception.action_exception_rule_confirm")
@api.multi @api.multi
def _check_exception(self): def _check_exception(self):
@ -266,5 +270,5 @@ class BaseException(models.AbstractModel):
""" """
exception_ids = self.detect_exceptions() exception_ids = self.detect_exceptions()
if exception_ids: if exception_ids:
exceptions = self.env['exception.rule'].browse(exception_ids) exceptions = self.env["exception.rule"].browse(exception_ids)
raise ValidationError('\n'.join(exceptions.mapped('name'))) raise ValidationError("\n".join(exceptions.mapped("name")))

View File

@ -1,4 +1,3 @@
from . import common from . import common
from . import purchase_test from . import purchase_test
from . import test_base_exception from . import test_base_exception

View File

@ -8,6 +8,7 @@ def setup_test_model(env, model_clses):
env.registry.setup_models(env.cr) env.registry.setup_models(env.cr)
env.registry.init_models( env.registry.init_models(
env.cr, [model_cls._name for model_cls in model_clses], env.cr,
dict(env.context, update_custom_fields=True) [model_cls._name for model_cls in model_clses],
dict(env.context, update_custom_fields=True),
) )

View File

@ -4,58 +4,63 @@ from odoo import api, fields, models
class PurchaseTest(models.Model): class PurchaseTest(models.Model):
_inherit = 'base.exception' _inherit = "base.exception"
_name = "base.exception.test.purchase" _name = "base.exception.test.purchase"
_description = "Base Ecxeption Test Model" _description = "Base Ecxeption Test Model"
name = fields.Char(required=True) name = fields.Char(required=True)
user_id = fields.Many2one('res.users', string='Responsible') user_id = fields.Many2one("res.users", string="Responsible")
state = fields.Selection( state = fields.Selection(
[('draft', 'New'), ('cancel', 'Cancelled'), [
('purchase', 'Purchase'), ("draft", "New"),
('to approve', 'To approve'), ('done', 'Done')], ("cancel", "Cancelled"),
string="Status", readonly=True, default='draft') ("purchase", "Purchase"),
("to approve", "To approve"),
("done", "Done"),
],
string="Status",
readonly=True,
default="draft",
)
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
partner_id = fields.Many2one('res.partner', string='Partner') partner_id = fields.Many2one("res.partner", string="Partner")
line_ids = fields.One2many( line_ids = fields.One2many("base.exception.test.purchase.line", "lead_id")
'base.exception.test.purchase.line', 'lead_id') amount_total = fields.Float(compute="_compute_amount_total", store=True)
amount_total = fields.Float(
compute='_compute_amount_total', store=True)
@api.depends('line_ids') @api.depends("line_ids")
def _compute_amount_total(cls): def _compute_amount_total(cls):
for record in cls: for record in cls:
for line in record.line_ids: for line in record.line_ids:
record.amount_total += line.amount * line.qty record.amount_total += line.amount * line.qty
@api.constrains('ignore_exception', 'line_ids', 'state') @api.constrains("ignore_exception", "line_ids", "state")
def test_purchase_check_exception(cls): def test_purchase_check_exception(cls):
orders = cls.filtered(lambda s: s.state == 'purchase') orders = cls.filtered(lambda s: s.state == "purchase")
if orders: if orders:
orders._check_exception() orders._check_exception()
@api.multi @api.multi
def button_approve(cls, force=False): def button_approve(cls, force=False):
cls.write({'state': 'to approve'}) cls.write({"state": "to approve"})
return {} return {}
@api.multi @api.multi
def button_draft(cls): def button_draft(cls):
cls.write({'state': 'draft'}) cls.write({"state": "draft"})
return {} return {}
@api.multi @api.multi
def button_confirm(cls): def button_confirm(cls):
cls.write({'state': 'purchase'}) cls.write({"state": "purchase"})
return True return True
@api.multi @api.multi
def button_cancel(cls): def button_cancel(cls):
cls.write({'state': 'cancel'}) cls.write({"state": "cancel"})
@api.multi @api.multi
def _reverse_field(self): def _reverse_field(self):
return 'test_purchase_ids' return "test_purchase_ids"
class LineTest(models.Model): class LineTest(models.Model):
@ -63,7 +68,6 @@ class LineTest(models.Model):
_description = "Base Exception Test Model Line" _description = "Base Exception Test Model Line"
name = fields.Char() name = fields.Char()
lead_id = fields.Many2one('base.exception.test.purchase', lead_id = fields.Many2one("base.exception.test.purchase", ondelete="cascade")
ondelete='cascade')
qty = fields.Float() qty = fields.Float()
amount = fields.Float() amount = fields.Float()

View File

@ -1,70 +1,79 @@
# Copyright 2016 Akretion Mourad EL HADJ MIMOUNE # Copyright 2016 Akretion Mourad EL HADJ MIMOUNE
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo.tests import common
from odoo.exceptions import ValidationError
from odoo import fields
from .common import setup_test_model
from .purchase_test import PurchaseTest, LineTest
import logging import logging
from odoo import fields
from odoo.exceptions import ValidationError
from odoo.tests import common
from .common import setup_test_model
from .purchase_test import LineTest, PurchaseTest
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@common.at_install(False) @common.at_install(False)
@common.post_install(True) @common.post_install(True)
class TestBaseException(common.SavepointCase): class TestBaseException(common.SavepointCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(TestBaseException, cls).setUpClass() super(TestBaseException, cls).setUpClass()
setup_test_model(cls.env, [PurchaseTest, LineTest]) setup_test_model(cls.env, [PurchaseTest, LineTest])
cls.base_exception = cls.env['base.exception'] cls.base_exception = cls.env["base.exception"]
cls.exception_rule = cls.env['exception.rule'] cls.exception_rule = cls.env["exception.rule"]
if 'test_purchase_ids' not in cls.exception_rule._fields: if "test_purchase_ids" not in cls.exception_rule._fields:
field = fields.Many2many('base.exception.test.purchase') field = fields.Many2many("base.exception.test.purchase")
cls.exception_rule._add_field('test_purchase_ids', field) cls.exception_rule._add_field("test_purchase_ids", field)
cls.exception_confirm = cls.env['exception.rule.confirm'] cls.exception_confirm = cls.env["exception.rule.confirm"]
cls.exception_rule._fields['model'].selection.append( cls.exception_rule._fields["model"].selection.append(
('base.exception.test.purchase', 'Purchase Order')) ("base.exception.test.purchase", "Purchase Order")
)
cls.exception_rule._fields['model'].selection.append( cls.exception_rule._fields["model"].selection.append(
('base.exception.test.purchase.line', 'Purchase Order Line')) ("base.exception.test.purchase.line", "Purchase Order Line")
)
cls.exceptionnozip = cls.env['exception.rule'].create({ cls.exceptionnozip = cls.env["exception.rule"].create(
'name': "No ZIP code on destination", {
'sequence': 10, "name": "No ZIP code on destination",
'model': "base.exception.test.purchase", "sequence": 10,
'code': "if not obj.partner_id.zip: failed=True", "model": "base.exception.test.purchase",
}) "code": "if not obj.partner_id.zip: failed=True",
}
)
cls.exceptionno_minorder = cls.env['exception.rule'].create({ cls.exceptionno_minorder = cls.env["exception.rule"].create(
'name': "Min order except", {
'sequence': 10, "name": "Min order except",
'model': "base.exception.test.purchase", "sequence": 10,
'code': "if obj.amount_total <= 200.0: failed=True", "model": "base.exception.test.purchase",
}) "code": "if obj.amount_total <= 200.0: failed=True",
}
)
cls.exceptionno_lineqty = cls.env['exception.rule'].create({ cls.exceptionno_lineqty = cls.env["exception.rule"].create(
'name': "Qty > 0", {
'sequence': 10, "name": "Qty > 0",
'model': "base.exception.test.purchase.line", "sequence": 10,
'code': "if obj.qty <= 0: failed=True" "model": "base.exception.test.purchase.line",
}) "code": "if obj.qty <= 0: failed=True",
}
)
def test_purchase_order_exception(self): def test_purchase_order_exception(self):
partner = self.env.ref('base.res_partner_1') partner = self.env.ref("base.res_partner_1")
partner.zip = False partner.zip = False
potest1 = self.env['base.exception.test.purchase'].create({ potest1 = self.env["base.exception.test.purchase"].create(
'name': 'Test base exception to basic purchase', {
'partner_id': partner.id, "name": "Test base exception to basic purchase",
'line_ids': [(0, 0, { "partner_id": partner.id,
'name': "line test", "line_ids": [
'amount': 120.0, (0, 0, {"name": "line test", "amount": 120.0, "qty": 1.5})
'qty': 1.5, ],
})], }
}) )
# Block because of exception during validation # Block because of exception during validation
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
potest1.button_confirm() potest1.button_confirm()
@ -73,4 +82,4 @@ class TestBaseException(common.SavepointCase):
# Test ignore exeception make possible for the po to validate # Test ignore exeception make possible for the po to validate
potest1.ignore_exception = True potest1.ignore_exception = True
potest1.button_confirm() potest1.button_confirm()
self.assertTrue(potest1.state == 'purchase') self.assertTrue(potest1.state == "purchase")

View File

@ -1,6 +1,6 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<odoo> <odoo>
<record id="view_exception_rule_tree" model="ir.ui.view"> <record id="view_exception_rule_tree" model="ir.ui.view">
<field name="name">exception.rule.tree</field> <field name="name">exception.rule.tree</field>
<field name="model">exception.rule</field> <field name="model">exception.rule</field>

View File

@ -1,2 +1 @@
from . import base_exception_confirm from . import base_exception_confirm

View File

@ -2,39 +2,36 @@
# Copyright 2017 Akretion (http://www.akretion.com) # Copyright 2017 Akretion (http://www.akretion.com)
# Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com> # Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models, _ from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class ExceptionRuleConfirm(models.AbstractModel): class ExceptionRuleConfirm(models.AbstractModel):
_name = 'exception.rule.confirm' _name = "exception.rule.confirm"
_description = 'Exception Rule Confirm Wizard' _description = "Exception Rule Confirm Wizard"
related_model_id = fields.Many2one('base.exception',) related_model_id = fields.Many2one("base.exception")
exception_ids = fields.Many2many( exception_ids = fields.Many2many(
'exception.rule', "exception.rule", string="Exceptions to resolve", readonly=True
string='Exceptions to resolve',
readonly=True,
) )
ignore = fields.Boolean('Ignore Exceptions') ignore = fields.Boolean("Ignore Exceptions")
@api.model @api.model
def default_get(self, field_list): def default_get(self, field_list):
res = super(ExceptionRuleConfirm, self).default_get(field_list) res = super(ExceptionRuleConfirm, self).default_get(field_list)
current_model = self.env.context.get('active_model') current_model = self.env.context.get("active_model")
model_except_obj = self.env[current_model] model_except_obj = self.env[current_model]
active_ids = self.env.context.get('active_ids') active_ids = self.env.context.get("active_ids")
if len(active_ids) > 1: if len(active_ids) > 1:
raise ValidationError( raise ValidationError(_("Only 1 ID accepted, got %r.") % active_ids)
_('Only 1 ID accepted, got %r.') % active_ids)
active_id = active_ids[0] active_id = active_ids[0]
related_model_except = model_except_obj.browse(active_id) related_model_except = model_except_obj.browse(active_id)
exception_ids = related_model_except.exception_ids.ids exception_ids = related_model_except.exception_ids.ids
res.update({'exception_ids': [(6, 0, exception_ids)]}) res.update({"exception_ids": [(6, 0, exception_ids)]})
res.update({'related_model_id': active_id}) res.update({"related_model_id": active_id})
return res return res
@api.multi @api.multi
def action_confirm(self): def action_confirm(self):
self.ensure_one() self.ensure_one()
return {'type': 'ir.actions.act_window_close'} return {"type": "ir.actions.act_window_close"}