Passed
Push — master ( 804f45...1fa2c0 )
by Jordi
04:08
created

AuditLogView.translate_state()   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nop 2
1
# -*- coding: utf-8 -*-
2
3
import collections
4
5
from bika.lims import api
6
from bika.lims import bikaMessageFactory as _
7
from bika.lims.api.snapshot import compare_snapshots
8
from bika.lims.api.snapshot import get_snapshot_by_version
9
from bika.lims.api.snapshot import get_snapshot_metadata
10
from bika.lims.api.snapshot import get_snapshot_version
11
from bika.lims.api.snapshot import get_snapshots
12
from bika.lims.browser.bika_listing import BikaListingView
13
from bika.lims.interfaces import IAuditable
14
from bika.lims.utils import t
15
from plone.memoize import view
16
from Products.CMFPlone.i18nl10n import ulocalized_time
17
from Products.Five.browser import BrowserView
18
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
19
20
21
class HasAuditLog(BrowserView):
22
    """Helper View to check if the context provides the IAuditable interface
23
    """
24
    def __call__(self):
25
        return IAuditable.providedBy(self.context)
26
27
28
class AuditLogView(BikaListingView):
29
    """Audit View
30
    """
31
    template = ViewPageTemplateFile("templates/auditlog.pt")
32
    diff_template = ViewPageTemplateFile("templates/auditlog_diff.pt")
33
34
    def __init__(self, context, request):
35
        super(AuditLogView, self).__init__(context, request)
36
37
        # Query is ignored in `folderitems` method and only there to override
38
        # the default settings
39
        self.catalog = "uid_catalog"
40
        self.contentFilter = {"UID": api.get_uid(context)}
41
42
        # TODO: Fix in senaite.core.listing.get_api_url
43
        #
44
        # Set the view name with `@@` prefix to get the right API URL on the
45
        # the setup folder
46
        self.__name__ = "@@auditlog"
47
48
        self.show_select_column = False
49
        self.show_search = False
50
        self.pagesize = 5
51
52
        self.icon = "{}/{}/{}".format(
53
            self.portal_url,
54
            "++resource++bika.lims.images",
55
            "%s_big.png" % context.portal_type.lower())
56
57
        self.title = "Audit Log for {}".format(api.get_title(context))
58
59
        self.columns = collections.OrderedDict((
60
            ("version", {
61
                "title": _("Version"), "sortable": False}),
62
            ("modified", {
63
                "title": _("Date Modified"), "sortable": False}),
64
            ("actor", {
65
                "title": _("Actor"), "sortable": False}),
66
            ("fullname", {
67
                "title": _("Fullname"), "sortable": False}),
68
            ("roles", {
69
                "title": _("Roles"), "sortable": False, "toggle": False}),
70
            ("remote_address", {
71
                "title": _("Remote IP"), "sortable": False}),
72
            ("action", {
73
                "title": _("Action"), "sortable": False}),
74
            ("review_state", {
75
                "title": _("Workflow State"), "sortable": False}),
76
            ("diff", {
77
                "title": _("Changes"), "sortable": False}),
78
        ))
79
80
        self.review_states = [
81
            {
82
                "id": "default",
83
                "title": "All",
84
                "contentFilter": {},
85
                "columns": self.columns.keys(),
86
            }
87
        ]
88
89
    def update(self):
90
        """Update hook
91
        """
92
        super(AuditLogView, self).update()
93
94 View Code Duplication
    def make_empty_item(self, **kw):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
95
        """Create a new empty item
96
        """
97
        item = {
98
            "uid": None,
99
            "before": {},
100
            "after": {},
101
            "replace": {},
102
            "allow_edit": [],
103
            "disabled": False,
104
            "state_class": "state-active",
105
        }
106
        item.update(**kw)
107
        return item
108
109
    @view.memoize
110
    def get_widget_for(self, fieldname):
111
        """Lookup the widget
112
        """
113
        field = self.context.getField(fieldname)
114
        if not field:
115
            return None
116
        return field.widget
117
118
    def get_widget_label_for(self, fieldname, default=None):
119
        """Lookup the widget of the field and return the label
120
        """
121
        widget = self.get_widget_for(fieldname)
122
        if widget is None:
123
            return default
124
        return widget.label
125
126
    def to_localized_time(self, date, **kw):
127
        """Converts the given date to a localized time string
128
        """
129
        date = api.to_date(date, default=None)
130
        if date is None:
131
            return ""
132
        # default options
133
        options = {
134
            "long_format": True,
135
            "time_only": False,
136
            "context": self.context,
137
            "request": self.request,
138
            "domain": "senaite.core",
139
        }
140
        options.update(kw)
141
        return ulocalized_time(date, **options)
142
143
    def render_diff(self, diff):
144
        """Render the diff template
145
146
        :param diff: Dictionary of fieldname -> diffs
147
        :returns: Rendered HTML template
148
        """
149
        return self.diff_template(self, diff=diff)
150
151
    def translate_state(self, s):
152
        """Translate the given state string
153
        """
154
        if not isinstance(s, basestring):
155
            return s
156
        s = s.capitalize().replace("_", " ")
157
        return t(_(s))
158
159
    def folderitems(self):
160
        """Generate folderitems for each version
161
        """
162
        items = []
163
        # get the snapshots
164
        snapshots = get_snapshots(self.context)
165
        # reverse the order to get the most recent change first
166
        snapshots = list(reversed(snapshots))
167
        # set the total number of items
168
        self.total = len(snapshots)
169
        # slice a batch
170
        batch = snapshots[self.limit_from:self.limit_from+self.pagesize]
171
172
        for snapshot in batch:
173
            item = self.make_empty_item(**snapshot)
174
            # get the version of the snapshot
175
            version = get_snapshot_version(self.context, snapshot)
176
177
            # Version
178
            item["version"] = version
179
180
            # get the metadata of the diff
181
            metadata = get_snapshot_metadata(snapshot)
182
183
            # Modification Date
184
            m_date = metadata.get("modified")
185
            item["modified"] = self.to_localized_time(m_date)
186
187
            # Actor
188
            actor = metadata.get("actor")
189
            item["actor"] = actor
190
191
            # Fullname
192
            properties = api.get_user_properties(actor)
193
            item["fullname"] = properties.get("fullname", actor)
194
195
            # Roles
196
            roles = metadata.get("roles", [])
197
            item["roles"] = ", ".join(roles)
198
199
            # Remote Address
200
            remote_address = metadata.get("remote_address")
201
            item["remote_address"] = remote_address
202
203
            # Action
204
            action = metadata.get("action")
205
            item["action"] = self.translate_state(action)
206
207
            # Review State
208
            review_state = metadata.get("review_state")
209
            item["review_state"] = self.translate_state(review_state)
210
211
            # get the previous snapshot
212
            prev_snapshot = get_snapshot_by_version(self.context, version-1)
213 View Code Duplication
            if prev_snapshot:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
214
                prev_metadata = get_snapshot_metadata(prev_snapshot)
215
                prev_review_state = prev_metadata.get("review_state")
216
                if prev_review_state != review_state:
217
                    item["replace"]["review_state"] = "{} → {}".format(
218
                        self.translate_state(prev_review_state),
219
                        self.translate_state(review_state))
220
221
                # Rendered Diff
222
                diff = compare_snapshots(snapshot, prev_snapshot)
223
                item["diff"] = self.render_diff(diff)
224
225
            # append the item
226
            items.append(item)
227
228
        return items
229