-
Notifications
You must be signed in to change notification settings - Fork 16
/
provenance.py
166 lines (140 loc) · 4.91 KB
/
provenance.py
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
from portality.dao import DomainObject
from portality.lib import dataobj
from portality.models import EditorGroup
class Provenance(dataobj.DataObj, DomainObject):
"""
{
"id" : "<provenance record id>",
"created_date" : "<when this action took place>",
"last_updated" : "<when this record was last updated>",
"user": "<user that carried out the action>",
"roles" : ["<roles this user has at the time of the event>"],
"editor_group": ["<list of editor groups the user was in at the time>"],
"type" : "<type being acted on: suggestion, journal, etc>",
"subtype" : "<inner type being acted on, in case you want to distinguish between applications/update requests, etc>",
"action" : "<string denoting the action taken on the object>",
"resource_id" : "<id of the type being acted on>"
}
"""
__type__ = "provenance"
def __init__(self, **kwargs):
# FIXME: hack, to deal with ES integration layer being improperly abstracted
if "_source" in kwargs:
kwargs = kwargs["_source"]
self._add_struct(PROVENANCE_STRUCT)
super(Provenance, self).__init__(raw=kwargs)
@property
def type(self):
return self._get_single("type")
@type.setter
def type(self, val):
self._set_with_struct("type", val)
@property
def user(self):
return self._get_single("user")
@user.setter
def user(self, val):
self._set_with_struct("user", val)
@property
def roles(self):
return self._get_list("roles")
@roles.setter
def roles(self, val):
self._set_with_struct("roles", val)
@property
def editor_group(self):
return self._get_list("editor_group")
@property
def subtype(self):
return self._get_single("subtype")
@property
def action(self):
return self._get_single("action")
@action.setter
def action(self, val):
self._set_with_struct("action", val)
@property
def resource_id(self):
return self._get_single("resource_id")
@resource_id.setter
def resource_id(self, val):
self._set_with_struct("resource_id", val)
def save(self, **kwargs):
# self.prep()
self.check_construct()
return super(Provenance, self).save(**kwargs)
@classmethod
def make(cls, account, action, obj, subtype=None, save=True):
egs1 = EditorGroup.groups_by_editor(account.id)
egs2 = EditorGroup.groups_by_associate(account.id)
egs = []
for eg in egs1:
if eg.id not in egs:
egs.append(eg.id)
for eg in egs2:
if eg.id not in egs:
egs.append(eg.id)
# FIXME: this is a back compatibility fix for when we change the index type from "suggestion" to "application",
# but we don't want to have to migrate the entire provenance system
objtyp = obj.__type__
if objtyp == "application":
objtyp = "suggestion"
d = {
"user" : account.id,
"roles" : account.role,
"type" : objtyp,
"action" : action,
"resource_id" : obj.id,
"editor_group" : egs
}
if subtype is not None:
d["subtype"] = subtype
p = Provenance(**d)
if save:
saved = p.save()
if saved is None:
raise ProvenanceException("Failed to save provenance record")
return p
@classmethod
def get_latest_by_resource_id(cls, resource_id):
q = ResourceIDQuery(resource_id)
# resp = cls.query(q=q.query())
# obs = [hit.get("_source") for hit in resp.get("hits", {}).get("hits", [])]
obs = cls.q2obj(q=q.query())
if len(obs) == 0:
return None
return Provenance(**obs[0])
PROVENANCE_STRUCT = {
"fields" : {
"id" : {"coerce" : "unicode"},
"created_date" : {"coerce" : "utcdatetime"},
"last_updated" : {"coerce" : "utcdatetime"},
"user" : {"coerce" : "unicode"},
"type" : {"coerce" : "unicode"},
"subtype" : {"coerce" : "unicode"},
"action" : {"coerce" : "unicode"},
"resource_id" : {"coerce" : "unicode"},
"es_type": {"coerce": "unicode"}
},
"lists" : {
"roles" : {"contains" : "field", "coerce" : "unicode"},
"editor_group" : {"contains" : "field", "coerce" : "unicode"}
}
}
class ResourceIDQuery(object):
def __init__(self, resource_id):
self.resource_id = resource_id
def query(self):
return {
"track_total_hits" : True,
"query" : {
"bool" : {
"must" : [
{"term" : {"resource_id.exact" : self.resource_id}}
]
}
},
"sort" : [{"created_date" : {"order" : "desc"}}]
}
class ProvenanceException(Exception):
pass