File: html_field_history_mixin.py

package info (click to toggle)
odoo 18.0.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 878,716 kB
  • sloc: javascript: 927,937; python: 685,670; xml: 388,524; sh: 1,033; sql: 415; makefile: 26
file content (140 lines) | stat: -rw-r--r-- 5,112 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import api, fields, models
from odoo.exceptions import ValidationError

from .diff_utils import apply_patch, generate_comparison, generate_patch


class HtmlFieldHistory(models.AbstractModel):
    _name = "html.field.history.mixin"
    _description = "Field html History"
    _html_field_history_size_limit = 300

    html_field_history = fields.Json("History data", prefetch=False)

    html_field_history_metadata = fields.Json(
        "History metadata", compute="_compute_metadata"
    )

    @api.model
    def _get_versioned_fields(self):
        """This method should be overriden

        :return: List[string]: A list of name of the fields to be versioned
        """
        return []

    @api.depends("html_field_history")
    def _compute_metadata(self):
        for rec in self:
            history_metadata = None
            if rec.html_field_history:
                history_metadata = {}
                for field_name in rec.html_field_history:
                    history_metadata[field_name] = []
                    for revision in rec.html_field_history[field_name]:
                        metadata = revision.copy()
                        metadata.pop("patch")
                        history_metadata[field_name].append(metadata)
            rec.html_field_history_metadata = history_metadata

    def write(self, vals):
        new_revisions = False
        db_contents = None
        versioned_fields = self._get_versioned_fields()
        vals_contain_versioned_fields = set(vals).intersection(versioned_fields)

        if vals_contain_versioned_fields:
            self.ensure_one()
            db_contents = dict([(f, self[f]) for f in versioned_fields])
            fields_data = self.env[self._name]._fields

            if any(f in vals and not fields_data[f].sanitize for f in versioned_fields):
                raise ValidationError(
                    "Ensure all versioned fields ( %s ) in model %s are declared as sanitize=True"
                    % (str(versioned_fields), self._name)
                )

        # Call super().write before generating the patch to be sure we perform
        # the diff on sanitized data
        write_result = super().write(vals)

        if not vals_contain_versioned_fields:
            return write_result

        history_revs = self.html_field_history or {}

        for field in versioned_fields:
            new_content = self[field] or ""

            if field not in history_revs:
                history_revs[field] = []

            old_content = db_contents[field] or ""
            if new_content != old_content:
                new_revisions = True
                patch = generate_patch(new_content, old_content)
                revision_id = (
                    (history_revs[field][0]["revision_id"] + 1)
                    if history_revs[field]
                    else 1
                )

                history_revs[field].insert(
                    0,
                    {
                        "patch": patch,
                        "revision_id": revision_id,
                        "create_date": self.env.cr.now().isoformat(),
                        "create_uid": self.env.uid,
                        "create_user_name": self.env.user.name,
                    },
                )
                limit = self._html_field_history_size_limit
                history_revs[field] = history_revs[field][:limit]
        # Call super().write again to include the new revision
        if new_revisions:
            extra_vals = {"html_field_history": history_revs}
            write_result = super().write(extra_vals) and write_result
        return write_result

    def html_field_history_get_content_at_revision(self, field_name, revision_id):
        """Get the requested field content restored at the revision_id.

        :param str field_name: the name of the field
        :param int revision_id: id of the last revision to restore

        :return: string: the restored content
        """
        self.ensure_one()

        revisions = [
            i
            for i in self.html_field_history[field_name]
            if i["revision_id"] >= revision_id
        ]

        content = self[field_name] or ""
        for revision in revisions:
            content = apply_patch(content, revision["patch"])

        return content

    def html_field_history_get_comparison_at_revision(self, field_name, revision_id):
        """For the requested field,
        Get a comparison between the current content of the field and the
        content restored at the requested revision_id.

        :param str field_name: the name of the field
        :param int revision_id: id of the last revision to compare

        :return: string: the comparison
        """
        self.ensure_one()
        restored_content = self.html_field_history_get_content_at_revision(
            field_name, revision_id
        )

        return generate_comparison(self[field_name] or "", restored_content)