diff --git a/setup/shopinvader_cache_invalidation/odoo/addons/shopinvader_cache_invalidation b/setup/shopinvader_cache_invalidation/odoo/addons/shopinvader_cache_invalidation new file mode 120000 index 0000000000..f21eeae892 --- /dev/null +++ b/setup/shopinvader_cache_invalidation/odoo/addons/shopinvader_cache_invalidation @@ -0,0 +1 @@ +../../../../shopinvader_cache_invalidation \ No newline at end of file diff --git a/setup/shopinvader_cache_invalidation/setup.py b/setup/shopinvader_cache_invalidation/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/shopinvader_cache_invalidation/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/shopinvader_cache_invalidation/__init__.py b/shopinvader_cache_invalidation/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/shopinvader_cache_invalidation/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/shopinvader_cache_invalidation/__manifest__.py b/shopinvader_cache_invalidation/__manifest__.py new file mode 100644 index 0000000000..8f774bd28d --- /dev/null +++ b/shopinvader_cache_invalidation/__manifest__.py @@ -0,0 +1,28 @@ +# Copyright 2023 Akretion (https://www.akretion.com). +# @author Matthieu SAISON +# License AGPL-3.0 or later (https: //www.gnu.org/licenses/agpl). + +{ + "name": "Custom Shopinvader Cache Purge", + "summary": "List url to purge and manage purge", + "version": "14.0.1.0.0", + "category": "Uncategorized", + "website": "https://github.com/shopinvader/odoo-shopinvader", + "author": " Akretion", + "license": "AGPL-3", + "application": False, + "installable": True, + "external_dependencies": { + "python": [], + "bin": [], + }, + "depends": ["shopinvader_search_engine"], + "data": [ + "data/ir_cron.xml", + "security/ir.model.access.csv", + "views/shopinvader_url_purge.xml", + "views/shopinvader_backend.xml", + "views/shopinvader_url_purge_type_view.xml", + ], + "demo": [], +} diff --git a/shopinvader_cache_invalidation/data/ir_cron.xml b/shopinvader_cache_invalidation/data/ir_cron.xml new file mode 100644 index 0000000000..dc04d6f564 --- /dev/null +++ b/shopinvader_cache_invalidation/data/ir_cron.xml @@ -0,0 +1,17 @@ + + + + Purge search engine cache from listed urls. + + + 1 + hours + -1 + + + code + model.search([("state", "=", "to_purge")]).purge_url() + + diff --git a/shopinvader_cache_invalidation/models/__init__.py b/shopinvader_cache_invalidation/models/__init__.py new file mode 100644 index 0000000000..2f6cc20b9d --- /dev/null +++ b/shopinvader_cache_invalidation/models/__init__.py @@ -0,0 +1,3 @@ +from . import se_binding +from . import shopinvader_url_purge +from . import shopinvader_backend diff --git a/shopinvader_cache_invalidation/models/se_binding.py b/shopinvader_cache_invalidation/models/se_binding.py new file mode 100644 index 0000000000..708c3ccf1c --- /dev/null +++ b/shopinvader_cache_invalidation/models/se_binding.py @@ -0,0 +1,33 @@ +# Copyright 2023 Akretion (https://www.akretion.com). +# @author Matthieu SAISON +# License AGPL-3.0 or later (https: //www.gnu.org/licenses/agpl). +# Copyright 2023 Akretion (https://www.akretion.com). +# @author Matthieu SAISON +# License AGPL-3.0 or later (https: //www.gnu.org/licenses/agpl). + +import logging + +from odoo import models + +_logger = logging.getLogger(__name__) + + +class SeBinding(models.AbstractModel): + _inherit = "se.binding" + + def synchronize(self): + res = super().synchronize() + for backend in self.mapped("backend_id"): + url_keys = { + binding.data.get("url_key") + for binding in self + if binding.backend_id == backend and binding.data.get("url_key") + } + for url_key in url_keys: + self.env["shopinvader.url.purge"]._request_purge( + url_key, + self._name, + self._description, + backend.id, + ) + return res diff --git a/shopinvader_cache_invalidation/models/shopinvader_backend.py b/shopinvader_cache_invalidation/models/shopinvader_backend.py new file mode 100644 index 0000000000..12061b108c --- /dev/null +++ b/shopinvader_cache_invalidation/models/shopinvader_backend.py @@ -0,0 +1,27 @@ +# Copyright 2023 Akretion (https://www.akretion.com). +# @author Matthieu SAISON +# License AGPL-3.0 or later (https: //www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ShopinvaderBackend(models.Model): + _inherit = "shopinvader.backend" + + purge_ids = fields.One2many( + "shopinvader.url.purge", "backend_id", string="Urls to purge" + ) + purge_nbr = fields.Integer(compute="_compute_purge_nbr", store=True, readonly=True) + + cache_refresh_secret = fields.Char() + + @property + def _server_env_fields(self): + env_fields = super()._server_env_fields + env_fields.update({"cache_refresh_secret": {}}) + return env_fields + + @api.depends("purge_ids") + def _compute_purge_nbr(self): + for record in self: + record.purge_nbr = len(record.purge_ids) diff --git a/shopinvader_cache_invalidation/models/shopinvader_url_purge.py b/shopinvader_cache_invalidation/models/shopinvader_url_purge.py new file mode 100644 index 0000000000..7e05ef4208 --- /dev/null +++ b/shopinvader_cache_invalidation/models/shopinvader_url_purge.py @@ -0,0 +1,98 @@ +# Copyright 2023 Akretion (https://www.akretion.com). +# @author Matthieu SAISON +# License AGPL-3.0 or later (https: //www.gnu.org/licenses/agpl). +import logging + +import requests +from urllib.parse import urljoin + +from odoo import fields, models + +_logger = logging.getLogger(__name__) + + +class ShopinvaderUrlPurge(models.Model): + _name = "shopinvader.url.purge" + _description = "List of url to purge from cache" + + url = fields.Char(required=True) + purge_type_id = fields.Many2one( + "shopinvader.url.purge.type", string="Purge Type", required=True + ) + backend_id = fields.Many2one("shopinvader.backend", string="Backend", required=True) + date_last_purge = fields.Datetime("Last Purge Date", readonly=True) + manual = fields.Boolean(default=True, readonly=True, required=True) + state = fields.Selection( + [ + ("to_purge", "To purge"), + ("done", "Done"), + ("failed", "Failed"), + ], + readonly=True, + ) + error = fields.Char(readonly=True) + + def _request_purge(self, url_key, model_name, model_description, backend_id): + record = self.search( + [ + ("url", "=", url_key), + ("backend_id", "=", backend_id), + ] + ) + if record: + record.state = "to_purge" + else: + purge_type = self.env["shopinvader.url.purge.type"].get_or_create( + model_name, model_description + ) + record = self.create( + { + "url": url_key, + "state": "to_purge", + "purge_type_id": purge_type.id, + "backend_id": backend_id, + "manual": False, + } + ) + + # use by cron + def purge_url(self): + for backend in self.backend_id: + s = requests.Session() + s.headers.update({"X-force-cache-refresh": backend.cache_refresh_secret}) + for record in self: + if record.backend_id == backend: + url = urljoin(record.backend_id.location, record.url) + try: + response = s.get(url) + if response.status_code != 200: + raise Exception( + "Response Error code %s %s", + response.status_code, + response.text, + ) + self.write( + { + "date_last_purge": fields.Datetime.now(), + "state": "done", + "error": None, + } + ) + except Exception as e: + _logger.error("Fail to purge %s", e) + self.error = str(e) + self.state = "failed" + continue + + +class ShopinvaderUrlPurgeType(models.Model): + _name = "shopinvader.url.purge.type" + + code = fields.Char(readonly=True) + name = fields.Char(required=True) + + def get_or_create(self, model_name, model_description): + res = self.search([("code", "=", model_name)]) + if not res: + res = self.create({"code": model_name, "name": model_description}) + return res diff --git a/shopinvader_cache_invalidation/readme/CONTRIBUTORS.rst b/shopinvader_cache_invalidation/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..c620df740d --- /dev/null +++ b/shopinvader_cache_invalidation/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Matthieu SAISON diff --git a/shopinvader_cache_invalidation/readme/DESCRIPTION.rst b/shopinvader_cache_invalidation/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..2d0ddbbed8 --- /dev/null +++ b/shopinvader_cache_invalidation/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Schedules tasks to purge urls's cache on shopinvader website diff --git a/shopinvader_cache_invalidation/security/ir.model.access.csv b/shopinvader_cache_invalidation/security/ir.model.access.csv new file mode 100644 index 0000000000..6cae8f7f66 --- /dev/null +++ b/shopinvader_cache_invalidation/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_custom_shopinvader_cache_purge,full access cache_purge,model_shopinvader_url_purge,base.group_user,1,1,1,1 +access_custom_shopinvader_cache_purge_type,full access cache_purge_type,model_shopinvader_url_purge_type,base.group_user,1,1,1,1 diff --git a/shopinvader_cache_invalidation/views/shopinvader_backend.xml b/shopinvader_cache_invalidation/views/shopinvader_backend.xml new file mode 100644 index 0000000000..6268088171 --- /dev/null +++ b/shopinvader_cache_invalidation/views/shopinvader_backend.xml @@ -0,0 +1,26 @@ + + + + + + shopinvader.backend + + +
+ +
+ + + + +
+ +
diff --git a/shopinvader_cache_invalidation/views/shopinvader_url_purge.xml b/shopinvader_cache_invalidation/views/shopinvader_url_purge.xml new file mode 100644 index 0000000000..57e8b2c96a --- /dev/null +++ b/shopinvader_cache_invalidation/views/shopinvader_url_purge.xml @@ -0,0 +1,129 @@ + + + + ir.actions.act_window + Liste des URL + shopinvader.url.purge + tree + [('backend_id', '=', active_id)] + + + + shopinvader.url.purge + + + + + + + + + + + + + + + + + + + shopinvader.url.purge + + + + + + + + + + + + + + shopinvader.url.purge + +
+
+
+ + + + + + + + + +
+
+
+ + + + shopinvader.url.purge + tree,form + [] + {} + + + + Set to purge + + action + code + + records.write({"state": "to_purge"}) + + + + + + + + Purge Url + + + code + + records.purge_url() + + + +
diff --git a/shopinvader_cache_invalidation/views/shopinvader_url_purge_type_view.xml b/shopinvader_cache_invalidation/views/shopinvader_url_purge_type_view.xml new file mode 100644 index 0000000000..ae37f2969f --- /dev/null +++ b/shopinvader_cache_invalidation/views/shopinvader_url_purge_type_view.xml @@ -0,0 +1,41 @@ + + + + + shopinvader.url.purge.type + + + + + + + + + + shopinvader.url.purge.type + + + + + + + + + + Purge Type + ir.actions.act_window + shopinvader.url.purge.type + tree + + [] + {} + + + + +