Coverage for portality/models/provenance.py: 98%
87 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-19 18:38 +0100
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-19 18:38 +0100
1from portality.dao import DomainObject
2from portality.lib import dataobj
3from portality.models import EditorGroup
5class Provenance(dataobj.DataObj, DomainObject):
6 """
7 {
8 "id" : "<provenance record id>",
9 "created_date" : "<when this action took place>",
10 "last_updated" : "<when this record was last updated>",
11 "user": "<user that carried out the action>",
12 "roles" : ["<roles this user has at the time of the event>"],
13 "editor_group": ["<list of editor groups the user was in at the time>"],
14 "type" : "<type being acted on: suggestion, journal, etc>",
15 "subtype" : "<inner type being acted on, in case you want to distinguish between applications/update requests, etc>",
16 "action" : "<string denoting the action taken on the object>",
17 "resource_id" : "<id of the type being acted on>"
18 }
19 """
21 __type__ = "provenance"
23 def __init__(self, **kwargs):
24 # FIXME: hack, to deal with ES integration layer being improperly abstracted
25 if "_source" in kwargs:
26 kwargs = kwargs["_source"]
27 self._add_struct(PROVENANCE_STRUCT)
28 super(Provenance, self).__init__(raw=kwargs)
30 @property
31 def type(self):
32 return self._get_single("type")
34 @type.setter
35 def type(self, val):
36 self._set_with_struct("type", val)
38 @property
39 def user(self):
40 return self._get_single("user")
42 @user.setter
43 def user(self, val):
44 self._set_with_struct("user", val)
46 @property
47 def roles(self):
48 return self._get_list("roles")
50 @roles.setter
51 def roles(self, val):
52 self._set_with_struct("roles", val)
54 @property
55 def editor_group(self):
56 return self._get_list("editor_group")
58 @property
59 def subtype(self):
60 return self._get_single("subtype")
62 @property
63 def action(self):
64 return self._get_single("action")
66 @action.setter
67 def action(self, val):
68 self._set_with_struct("action", val)
70 @property
71 def resource_id(self):
72 return self._get_single("resource_id")
74 @resource_id.setter
75 def resource_id(self, val):
76 self._set_with_struct("resource_id", val)
78 def save(self, **kwargs):
79 # self.prep()
80 self.check_construct()
81 return super(Provenance, self).save(**kwargs)
83 @classmethod
84 def make(cls, account, action, obj, subtype=None, save=True):
85 egs1 = EditorGroup.groups_by_editor(account.id)
86 egs2 = EditorGroup.groups_by_associate(account.id)
87 egs = []
88 for eg in egs1:
89 if eg.id not in egs:
90 egs.append(eg.id)
91 for eg in egs2:
92 if eg.id not in egs:
93 egs.append(eg.id)
95 # FIXME: this is a back compatibility fix for when we change the index type from "suggestion" to "application",
96 # but we don't want to have to migrate the entire provenance system
97 objtyp = obj.__type__
98 if objtyp == "application":
99 objtyp = "suggestion"
101 d = {
102 "user" : account.id,
103 "roles" : account.role,
104 "type" : objtyp,
105 "action" : action,
106 "resource_id" : obj.id,
107 "editor_group" : egs
108 }
109 if subtype is not None:
110 d["subtype"] = subtype
112 p = Provenance(**d)
113 if save:
114 saved = p.save()
115 if saved is None:
116 raise ProvenanceException("Failed to save provenance record")
117 return p
119 @classmethod
120 def get_latest_by_resource_id(cls, resource_id):
121 q = ResourceIDQuery(resource_id)
122 # resp = cls.query(q=q.query())
123 # obs = [hit.get("_source") for hit in resp.get("hits", {}).get("hits", [])]
124 obs = cls.q2obj(q=q.query())
125 if len(obs) == 0:
126 return None
127 return Provenance(**obs[0])
130PROVENANCE_STRUCT = {
131 "fields" : {
132 "id" : {"coerce" : "unicode"},
133 "created_date" : {"coerce" : "utcdatetime"},
134 "last_updated" : {"coerce" : "utcdatetime"},
135 "user" : {"coerce" : "unicode"},
136 "type" : {"coerce" : "unicode"},
137 "subtype" : {"coerce" : "unicode"},
138 "action" : {"coerce" : "unicode"},
139 "resource_id" : {"coerce" : "unicode"},
140 "es_type": {"coerce": "unicode"}
141 },
142 "lists" : {
143 "roles" : {"contains" : "field", "coerce" : "unicode"},
144 "editor_group" : {"contains" : "field", "coerce" : "unicode"}
145 }
146}
148class ResourceIDQuery(object):
149 def __init__(self, resource_id):
150 self.resource_id = resource_id
152 def query(self):
153 return {
154 "track_total_hits" : True,
155 "query" : {
156 "bool" : {
157 "must" : [
158 {"term" : {"resource_id.exact" : self.resource_id}}
159 ]
160 }
161 },
162 "sort" : [{"created_date" : {"order" : "desc"}}]
163 }
165class ProvenanceException(Exception):
166 pass