|
| 1 | +# Copyright 2024 Akretion (http://www.akretion.com). |
| 2 | +# @author Florian Mounier <florian.mounier@akretion.com> |
| 3 | +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
| 4 | + |
| 5 | +from urllib.parse import urlencode |
| 6 | + |
| 7 | +import requests |
| 8 | + |
| 9 | +from odoo import api, fields, models |
| 10 | +from odoo.exceptions import UserError |
| 11 | + |
| 12 | + |
| 13 | +class CrossConnectServer(models.Model): |
| 14 | + _name = "cross.connect.server" |
| 15 | + _description = "Cross Connect Server" |
| 16 | + _inherit = "server.env.mixin" |
| 17 | + |
| 18 | + name = fields.Char( |
| 19 | + required=True, |
| 20 | + help="This name will be used for the new created app", |
| 21 | + ) |
| 22 | + server_url = fields.Char(required=True) |
| 23 | + api_key = fields.Char( |
| 24 | + required=True, |
| 25 | + ) |
| 26 | + group_ids = fields.One2many( |
| 27 | + "res.groups", |
| 28 | + inverse_name="cross_connect_server_id", |
| 29 | + string="Cross Connect Server Groups", |
| 30 | + readonly=True, |
| 31 | + ) |
| 32 | + menu_id = fields.Many2one( |
| 33 | + "ir.ui.menu", |
| 34 | + string="Menu", |
| 35 | + help="Menu to display the Cross Connect Server in the menu", |
| 36 | + compute="_compute_menu_id", |
| 37 | + store=True, |
| 38 | + ) |
| 39 | + web_icon_data = fields.Binary( |
| 40 | + compute="_compute_web_icon_data", inverse="_inverse_web_icon_data" |
| 41 | + ) |
| 42 | + |
| 43 | + @api.depends("name", "group_ids") |
| 44 | + def _compute_menu_id(self): |
| 45 | + for record in self: |
| 46 | + if not record.group_ids: |
| 47 | + if record.menu_id: |
| 48 | + if record.menu_id.action: |
| 49 | + record.menu_id.action.unlink() |
| 50 | + record.menu_id.unlink() |
| 51 | + record.menu_id = False |
| 52 | + continue |
| 53 | + |
| 54 | + menu_groups = record.group_ids |
| 55 | + |
| 56 | + if not record.menu_id: |
| 57 | + action = self.env["ir.actions.act_url"].create( |
| 58 | + { |
| 59 | + "name": record.name, |
| 60 | + "url": f"/cross_connect_server/{record.id}", |
| 61 | + "target": "new", |
| 62 | + } |
| 63 | + ) |
| 64 | + icon = "cross_connect_client,static/description/web_icon_data.png" |
| 65 | + record.menu_id = self.env["ir.ui.menu"].create( |
| 66 | + { |
| 67 | + "name": record.name, |
| 68 | + "action": f"ir.actions.act_url,{action.id}", # noqa |
| 69 | + "web_icon": icon, |
| 70 | + "groups_id": [(6, 0, menu_groups.ids)], |
| 71 | + "sequence": 100, |
| 72 | + } |
| 73 | + ) |
| 74 | + else: |
| 75 | + record.menu_id.name = record.name |
| 76 | + record.menu_id.groups_id = [(6, 0, menu_groups.ids)] |
| 77 | + |
| 78 | + @api.depends("menu_id") |
| 79 | + def _compute_web_icon_data(self): |
| 80 | + for record in self: |
| 81 | + record.web_icon_data = record.menu_id.web_icon_data |
| 82 | + |
| 83 | + def _inverse_web_icon_data(self): |
| 84 | + for record in self: |
| 85 | + record.menu_id.web_icon_data = record.web_icon_data |
| 86 | + |
| 87 | + def _absolute_url_for(self, path): |
| 88 | + return f"{self.server_url.rstrip('/')}/cross_connect/{path.lstrip('/')}" |
| 89 | + |
| 90 | + def _request(self, method, url, headers=None, data=None): |
| 91 | + headers = headers or {} |
| 92 | + headers["api-key"] = self.api_key |
| 93 | + response = requests.request( |
| 94 | + method, |
| 95 | + self._absolute_url_for(url), |
| 96 | + headers=headers, |
| 97 | + json=data, |
| 98 | + timeout=10, |
| 99 | + ) |
| 100 | + response.raise_for_status() |
| 101 | + return response.json() |
| 102 | + |
| 103 | + def _get_cross_connect_url(self, **params): |
| 104 | + self.ensure_one() |
| 105 | + groups = self.env.user.groups_id & self.group_ids |
| 106 | + if not groups: |
| 107 | + raise UserError(self.env._("You are not allowed to access this server")) |
| 108 | + |
| 109 | + if not self.env.user.email: |
| 110 | + raise UserError(self.env._("User email is required")) |
| 111 | + |
| 112 | + data = { |
| 113 | + "id": self.env.user.id, |
| 114 | + "name": self.env.user.name, |
| 115 | + "login": self.env.user.login, |
| 116 | + "email": self.env.user.email, |
| 117 | + "lang": self.env.user.lang, |
| 118 | + "groups": [group.cross_connect_server_group_id for group in groups], |
| 119 | + } |
| 120 | + |
| 121 | + response = self._request("POST", "/access", data=data) |
| 122 | + client_id = response.get("client_id") |
| 123 | + token = response.get("token") |
| 124 | + if not token: |
| 125 | + raise UserError(self.env._("Missing token")) |
| 126 | + |
| 127 | + url = f"login/{client_id}/{token}" |
| 128 | + if params: |
| 129 | + url += "?" + urlencode(params) |
| 130 | + |
| 131 | + return self._absolute_url_for(url) |
| 132 | + |
| 133 | + def _sync_groups(self): |
| 134 | + self.ensure_one() |
| 135 | + response = self._request("GET", "/sync") |
| 136 | + remote_groups = response.get("groups", []) |
| 137 | + # Removing groups that are not on the remote server |
| 138 | + remote_groups_ids = {remote_group["id"] for remote_group in remote_groups} |
| 139 | + self.group_ids.filtered( |
| 140 | + lambda group: group.cross_connect_server_group_id not in remote_groups_ids |
| 141 | + ).write({"cross_connect_server_id": False}) |
| 142 | + |
| 143 | + # Create or Update existing groups |
| 144 | + for remote_group in remote_groups: |
| 145 | + existing_group = self.env["res.groups"].search( |
| 146 | + [("cross_connect_server_group_id", "=", remote_group["id"])] |
| 147 | + ) |
| 148 | + if existing_group and not existing_group.cross_connect_server_id: |
| 149 | + existing_group.write({"cross_connect_server_id": self.id}) |
| 150 | + if existing_group: |
| 151 | + existing_group.sudo().write( |
| 152 | + { |
| 153 | + "name": f"{self.name}: {remote_group['name']}", |
| 154 | + "comment": remote_group["comment"], |
| 155 | + } |
| 156 | + ) |
| 157 | + else: |
| 158 | + self.env["res.groups"].sudo().create( |
| 159 | + { |
| 160 | + "cross_connect_server_id": self.id, |
| 161 | + "cross_connect_server_group_id": remote_group["id"], |
| 162 | + "name": f"{self.name}: {remote_group['name']}", |
| 163 | + "comment": remote_group["comment"], |
| 164 | + } |
| 165 | + ) |
| 166 | + |
| 167 | + def action_sync(self): |
| 168 | + for record in self: |
| 169 | + record._sync_groups() |
| 170 | + |
| 171 | + def action_disable(self): |
| 172 | + for record in self: |
| 173 | + record.group_ids.write({"cross_connect_server_id": False}) |
| 174 | + |
| 175 | + @property |
| 176 | + def _server_env_fields(self): |
| 177 | + return {"api_key": {}} |
| 178 | + |
| 179 | + def unlink(self): |
| 180 | + for rec in self: |
| 181 | + # deleting the groups will delete the menu and related action. |
| 182 | + rec.group_ids.unlink() |
| 183 | + return super().unlink() |
0 commit comments