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

1from portality.dao import DomainObject 

2from portality.lib import dataobj 

3from portality.models import EditorGroup 

4 

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 """ 

20 

21 __type__ = "provenance" 

22 

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) 

29 

30 @property 

31 def type(self): 

32 return self._get_single("type") 

33 

34 @type.setter 

35 def type(self, val): 

36 self._set_with_struct("type", val) 

37 

38 @property 

39 def user(self): 

40 return self._get_single("user") 

41 

42 @user.setter 

43 def user(self, val): 

44 self._set_with_struct("user", val) 

45 

46 @property 

47 def roles(self): 

48 return self._get_list("roles") 

49 

50 @roles.setter 

51 def roles(self, val): 

52 self._set_with_struct("roles", val) 

53 

54 @property 

55 def editor_group(self): 

56 return self._get_list("editor_group") 

57 

58 @property 

59 def subtype(self): 

60 return self._get_single("subtype") 

61 

62 @property 

63 def action(self): 

64 return self._get_single("action") 

65 

66 @action.setter 

67 def action(self, val): 

68 self._set_with_struct("action", val) 

69 

70 @property 

71 def resource_id(self): 

72 return self._get_single("resource_id") 

73 

74 @resource_id.setter 

75 def resource_id(self, val): 

76 self._set_with_struct("resource_id", val) 

77 

78 def save(self, **kwargs): 

79 # self.prep() 

80 self.check_construct() 

81 return super(Provenance, self).save(**kwargs) 

82 

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) 

94 

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" 

100 

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 

111 

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 

118 

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]) 

128 

129 

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} 

147 

148class ResourceIDQuery(object): 

149 def __init__(self, resource_id): 

150 self.resource_id = resource_id 

151 

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 } 

164 

165class ProvenanceException(Exception): 

166 pass