|  | 
|  | 1 | +import base64 | 
|  | 2 | + | 
|  | 3 | +import polib | 
|  | 4 | + | 
|  | 5 | +from odoo import Command, _, api, fields, models | 
|  | 6 | +from odoo.exceptions import UserError, ValidationError | 
|  | 7 | +from odoo.tools import format_list | 
|  | 8 | + | 
|  | 9 | +from odoo import _, api, Command, fields, models | 
|  | 10 | +from odoo.exceptions import UserError, ValidationError | 
|  | 11 | +from odoo.tools import format_list | 
|  | 12 | + | 
|  | 13 | +class Resource(models.Model): | 
|  | 14 | +    _name = "t9n.resource" | 
|  | 15 | +    _description = "Resource file" | 
|  | 16 | + | 
|  | 17 | +    file_name = fields.Char() | 
|  | 18 | +    file = fields.Binary("Resource File", store=False) | 
|  | 19 | +    message_ids = fields.One2many( | 
|  | 20 | +        comodel_name="t9n.message", | 
|  | 21 | +        inverse_name="resource_id", | 
|  | 22 | +        string="Entries to translate", | 
|  | 23 | +    ) | 
|  | 24 | +    project_id = fields.Many2one( | 
|  | 25 | +        comodel_name="t9n.project", | 
|  | 26 | +    ) | 
|  | 27 | + | 
|  | 28 | +    _sql_constraints = [ | 
|  | 29 | +        ( | 
|  | 30 | +            "file_name_project_id_unique", | 
|  | 31 | +            "unique(file_name, project_id)", | 
|  | 32 | +            "A file with the same name already exists in the same project!", | 
|  | 33 | +        ), | 
|  | 34 | +    ] | 
|  | 35 | + | 
|  | 36 | +    def _decode_resource_file(self, resource_file): | 
|  | 37 | +        try: | 
|  | 38 | +            file_content = base64.b64decode(resource_file).decode() | 
|  | 39 | +            po_obj = polib.pofile(file_content) | 
|  | 40 | +        except (IOError, UnicodeDecodeError): | 
|  | 41 | +            po_obj = [] | 
|  | 42 | +        return [ | 
|  | 43 | +            { | 
|  | 44 | +                "body": entry.msgid, | 
|  | 45 | +                "context": entry.msgctxt, | 
|  | 46 | +                "translator_comments": entry.tcomment, | 
|  | 47 | +                "extracted_comments": entry.comment, | 
|  | 48 | +                "references": "\n".join([fpath + (lineno and f":{lineno}") for fpath, lineno in entry.occurrences]), | 
|  | 49 | +            } | 
|  | 50 | +            for entry in po_obj | 
|  | 51 | +        ] | 
|  | 52 | + | 
|  | 53 | +    @api.model_create_multi | 
|  | 54 | +    def create(self, vals_list): | 
|  | 55 | +        broken_files = [] | 
|  | 56 | +        for vals in vals_list: | 
|  | 57 | +            if not vals.get("file"): | 
|  | 58 | +                raise ValidationError(_("A resource file is required to create a resource.")) | 
|  | 59 | +            po_obj = self._decode_resource_file(vals["file"]) | 
|  | 60 | +            del vals["file"] | 
|  | 61 | +            if not po_obj: | 
|  | 62 | +                broken_files.append(vals["file_name"]) | 
|  | 63 | +                continue | 
|  | 64 | +            vals["message_ids"] = [Command.create(message) for message in po_obj] | 
|  | 65 | +        if broken_files: | 
|  | 66 | +            raise UserError( | 
|  | 67 | +                _( | 
|  | 68 | +                    "Resource files must be valid .pot files. The following files are ill-formatted or empty: %(file_names)s", | 
|  | 69 | +                    file_names=format_list(self.env, broken_files), | 
|  | 70 | +                ), | 
|  | 71 | +            ) | 
|  | 72 | +        return super().create(vals_list) | 
|  | 73 | + | 
|  | 74 | +    def write(self, vals): | 
|  | 75 | +        self.ensure_one() | 
|  | 76 | +        if "file" not in vals: | 
|  | 77 | +            return super().write(vals) | 
|  | 78 | +        po_obj = self._decode_resource_file(vals["file"]) | 
|  | 79 | +        del vals["file"] | 
|  | 80 | +        if not po_obj: | 
|  | 81 | +            raise UserError( | 
|  | 82 | +                _("The files: %(file_name)s should be a .po file with a valid syntax.", file_name=vals["file_name"]), | 
|  | 83 | +            ) | 
|  | 84 | +        current_msgs_by_tuple = {(msg.body, msg.context): msg for msg in self.message_ids} | 
|  | 85 | +        new_msgs_by_tuple = {(msg["body"], msg["context"]): msg for msg in po_obj} | 
|  | 86 | +        to_create = [msg_val for key, msg_val in new_msgs_by_tuple.items() if key not in current_msgs_by_tuple] | 
|  | 87 | +        to_unlink = {msg.id for key, msg in current_msgs_by_tuple.items() if key not in new_msgs_by_tuple} | 
|  | 88 | +        to_update = [ | 
|  | 89 | +            (current_msgs_by_tuple[key].id, new_msgs_by_tuple[key]) | 
|  | 90 | +            for key in set(current_msgs_by_tuple) & set(new_msgs_by_tuple) | 
|  | 91 | +        ] | 
|  | 92 | +        vals["message_ids"] = ( | 
|  | 93 | +            [Command.create(vals) for vals in to_create] | 
|  | 94 | +            + [Command.unlink(id) for id in to_unlink] | 
|  | 95 | +            + [Command.update(id, vals) for id, vals in to_update] | 
|  | 96 | +        ) | 
|  | 97 | +        return super().write(vals) | 
0 commit comments