Coverage for portality/models/v2/application.py: 79%

207 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-09-20 23:33 +0100

1from copy import deepcopy 

2 

3from portality import constants 

4from portality.core import app 

5from portality.lib import es_data_mapping 

6from portality.models.v2 import shared_structs 

7from portality.models.v2.journal import JournalLikeObject, Journal 

8from portality.lib.coerce import COERCE_MAP 

9from portality.dao import DomainObject 

10 

11APPLICATION_STRUCT = { 

12 "objects": [ 

13 "admin", "index" 

14 ], 

15 

16 "structs": { 

17 "admin": { 

18 "fields": { 

19 "current_journal": {"coerce": "unicode"}, 

20 "related_journal": {"coerce": "unicode"}, 

21 "application_status": {"coerce": "unicode"}, 

22 "date_applied": {"coerce": "utcdatetime"}, 

23 "application_type": { 

24 "coerce": "unicode", 

25 "allowed_values": [ 

26 constants.APPLICATION_TYPE_NEW_APPLICATION, 

27 constants.APPLICATION_TYPE_UPDATE_REQUEST 

28 ] 

29 } 

30 } 

31 }, 

32 "index": { 

33 "fields": { 

34 "application_type": {"coerce": "unicode"} 

35 } 

36 } 

37 } 

38} 

39 

40 

41class Application(JournalLikeObject): 

42 __type__ = "application" 

43 

44 __SEAMLESS_STRUCT__ = [ 

45 shared_structs.JOURNAL_BIBJSON, 

46 shared_structs.SHARED_JOURNAL_LIKE, 

47 APPLICATION_STRUCT 

48 ] 

49 

50 __SEAMLESS_COERCE__ = COERCE_MAP 

51 

52 def __init__(self, **kwargs): 

53 # FIXME: hack, to deal with ES integration layer being improperly abstracted 

54 if "_source" in kwargs: 

55 kwargs = kwargs["_source"] 

56 super(Application, self).__init__(raw=kwargs) 

57 if self.application_type is None: 

58 if self.current_journal: 

59 self.set_is_update_request(True) 

60 else: 

61 self.set_is_update_request(False) 

62 

63 @classmethod 

64 def get_by_owner(cls, owner): 

65 q = SuggestionQuery(owner=owner) 

66 result = cls.query(q=q.query()) 

67 records = [cls(**r.get("_source")) for r in result.get("hits", {}).get("hits", [])] 

68 return records 

69 

70 @classmethod 

71 def delete_selected(cls, email=None, statuses=None): 

72 q = SuggestionQuery(email=email, statuses=statuses) 

73 r = cls.delete_by_query(q.query()) 

74 

75 @classmethod 

76 def list_by_status(cls, status): 

77 q = StatusQuery(status) 

78 return cls.iterate(q=q.query()) 

79 

80 @classmethod 

81 def find_latest_by_current_journal(cls, journal_id): 

82 q = CurrentJournalQuery(journal_id) 

83 results = cls.q2obj(q=q.query()) 

84 if len(results) > 0: 

85 return results[0] 

86 return None 

87 

88 @classmethod 

89 def find_all_by_related_journal(cls, journal_id): 

90 q = RelatedJournalQuery(journal_id, size=1000) 

91 return cls.q2obj(q=q.query()) 

92 

93 @classmethod 

94 def assignment_to_editor_groups(cls, egs): 

95 q = AssignedEditorGroupsQuery([eg.name for eg in egs]) 

96 res = cls.query(q.query()) 

97 buckets = res.get("aggregations", {}).get("editor_groups", {}).get("buckets", []) 

98 assignments = {} 

99 for b in buckets: 

100 assignments[b.get("key")] = b.get("doc_count") 

101 return assignments 

102 

103 def mappings(self): 

104 return es_data_mapping.create_mapping(self.__seamless_struct__.raw, MAPPING_OPTS) 

105 

106 @property 

107 def application_type(self): 

108 return self.__seamless__.get_single("admin.application_type") 

109 

110 @application_type.setter 

111 def application_type(self, val): 

112 self.__seamless__.set_with_struct("admin.application_type", val) 

113 

114 def set_is_update_request(self, val): 

115 application_type = constants.APPLICATION_TYPE_UPDATE_REQUEST if val is True else constants.APPLICATION_TYPE_NEW_APPLICATION 

116 self.application_type = application_type 

117 

118 @property 

119 def current_journal(self): 

120 return self.__seamless__.get_single("admin.current_journal") 

121 

122 def set_current_journal(self, journal_id): 

123 # anything that has a current journal is, by definition, an update request 

124 self.set_is_update_request(True) 

125 self.__seamless__.set_with_struct("admin.current_journal", journal_id) 

126 

127 def remove_current_journal(self): 

128 self.__seamless__.delete("admin.current_journal") 

129 

130 @property 

131 def related_journal(self): 

132 return self.__seamless__.get_single("admin.related_journal") 

133 

134 def set_related_journal(self, journal_id): 

135 self.__seamless__.set_with_struct("admin.related_journal", journal_id) 

136 

137 def remove_related_journal(self): 

138 self.__seamless__.delete("admin.related_journal") 

139 

140 @property 

141 def related_journal_object(self): 

142 if self.related_journal: 

143 return Journal.pull(self.related_journal) 

144 return None 

145 

146 @property 

147 def application_status(self): 

148 return self.__seamless__.get_single("admin.application_status") 

149 

150 def set_application_status(self, val): 

151 self.__seamless__.set_with_struct("admin.application_status", val) 

152 

153 @property 

154 def date_applied(self): 

155 return self.__seamless__.get_single("admin.date_applied") 

156 

157 @date_applied.setter 

158 def date_applied(self, val): 

159 self.__seamless__.set_with_struct("admin.date_applied", val) 

160 

161 def _sync_owner_to_journal(self): 

162 if self.current_journal is None: 

163 return 

164 from portality.models.v2.journal import Journal 

165 cj = Journal.pull(self.current_journal) 

166 if cj is not None and cj.owner != self.owner: 

167 cj.set_owner(self.owner) 

168 cj.save(sync_owner=False) 

169 

170 def _generate_index(self): 

171 super(Application, self)._generate_index() 

172 

173 # index_record_type = None 

174 # if self.application_type == constants.APPLICATION_TYPE_NEW_APPLICATION: 

175 # if self.application_status in [constants.APPLICATION_STATUS_REJECTED, constants.APPLICATION_STATUS_ACCEPTED]: 

176 # index_record_type = constants.INDEX_RECORD_TYPE_NEW_APPLICATION_FINISHED 

177 # else: 

178 # index_record_type = constants.INDEX_RECORD_TYPE_NEW_APPLICATION_UNFINISHED 

179 # elif self.application_type == constants.APPLICATION_TYPE_UPDATE_REQUEST: 

180 # if self.application_status in [constants.APPLICATION_STATUS_REJECTED, constants.APPLICATION_STATUS_ACCEPTED]: 

181 # index_record_type = constants.INDEX_RECORD_TYPE_UPDATE_REQUEST_FINISHED 

182 # else: 

183 # index_record_type = constants.INDEX_RECORD_TYPE_UPDATE_REQUEST_UNFINISHED 

184 # if index_record_type is not None: 

185 # self.__seamless__.set_with_struct("index.application_type", index_record_type) 

186 

187 # FIXME: Temporary partial reversion of an indexing change (this index.application_type data is still being used 

188 # in applications search) 

189 if self.current_journal is not None: 

190 self.__seamless__.set_with_struct("index.application_type", "update request") 

191 elif self.application_status in [constants.APPLICATION_STATUS_ACCEPTED, constants.APPLICATION_STATUS_REJECTED]: 

192 self.__seamless__.set_with_struct("index.application_type", "finished application/update") 

193 else: 

194 self.__seamless__.set_with_struct("index.application_type", "new application") 

195 

196 def prep(self, is_update=True): 

197 self._generate_index() 

198 if is_update: 

199 self.set_last_updated() 

200 

201 def save(self, sync_owner=True, **kwargs): 

202 self.prep() 

203 self.verify_against_struct() 

204 if sync_owner: 

205 self._sync_owner_to_journal() 

206 return super(Application, self).save(**kwargs) 

207 

208 ######################################################### 

209 ## DEPRECATED METHODS 

210 

211 @property 

212 def suggested_on(self): 

213 return self.date_applied 

214 

215 @suggested_on.setter 

216 def suggested_on(self, val): 

217 self.date_applied = val 

218 

219 @property 

220 def suggester(self): 

221 owner = self.owner 

222 if owner is None: 

223 return None 

224 from portality.models import Account 

225 oacc = Account.pull(owner) 

226 if oacc is None: 

227 return None 

228 return { 

229 "name": owner, 

230 "email": oacc.email 

231 } 

232 

233 

234class DraftApplication(Application): 

235 __type__ = "draft_application" 

236 

237 __SEAMLESS_APPLY_STRUCT_ON_INIT__ = False 

238 __SEAMLESS_CHECK_REQUIRED_ON_INIT__ = False 

239 

240 

241class AllPublisherApplications(DomainObject): 

242 __type__ = "draft_application,application" 

243 

244 

245MAPPING_OPTS = { 

246 "dynamic": None, 

247 "coerces": app.config["DATAOBJ_TO_MAPPING_DEFAULTS"], 

248 "exceptions": { 

249 "admin.notes.note": { 

250 "type": "text", 

251 "index": False, 

252 # "include_in_all": False # Removed in es6 fixme: do we need to look at copy_to for the mapping? 

253 } 

254 } 

255} 

256 

257 

258class SuggestionQuery(object): 

259 _base_query = {"track_total_hits": True, "query": {"bool": {"must": []}}} 

260 _email_term = {"term": {"admin.applicant.email.exact": "<email address>"}} 

261 _status_terms = {"terms": {"admin.application_status.exact": ["<list of statuses>"]}} 

262 _owner_term = {"term": {"admin.owner.exact": "<the owner id>"}} 

263 

264 def __init__(self, email=None, statuses=None, owner=None): 

265 self.email = email 

266 self.owner = owner 

267 if statuses: 

268 self.statuses = statuses 

269 else: 

270 self.statuses = [] 

271 

272 def query(self): 

273 q = deepcopy(self._base_query) 

274 if self.email: 

275 et = deepcopy(self._email_term) 

276 et["term"]["admin.applicant.email.exact"] = self.email 

277 q["query"]["bool"]["must"].append(et) 

278 if self.statuses and len(self.statuses) > 0: 

279 st = deepcopy(self._status_terms) 

280 st["terms"]["admin.application_status.exact"] = self.statuses 

281 q["query"]["bool"]["must"].append(st) 

282 

283 if self.owner is not None: 

284 ot = deepcopy(self._owner_term) 

285 ot["term"]["admin.owner.exact"] = self.owner 

286 q["query"]["bool"]["must"].append(ot) 

287 return q 

288 

289 

290class OwnerStatusQuery(object): 

291 base_query = { 

292 "track_total_hits": True, 

293 "query": { 

294 "bool": { 

295 "must": [] 

296 } 

297 }, 

298 "sort": [ 

299 {"created_date": "desc"} 

300 ], 

301 "size": 10 

302 } 

303 

304 def __init__(self, owner, statuses, size=10): 

305 self._query = deepcopy(self.base_query) 

306 owner_term = {"match": {"owner": owner}} 

307 self._query["query"]["bool"]["must"].append(owner_term) 

308 status_term = {"terms": {"admin.application_status.exact": statuses}} 

309 self._query["query"]["bool"]["must"].append(status_term) 

310 self._query["size"] = size 

311 

312 def query(self): 

313 return self._query 

314 

315 

316class StatusQuery(object): 

317 

318 def __init__(self, statuses): 

319 if not isinstance(statuses, list): 

320 statuses = [statuses] 

321 self.statuses = statuses 

322 

323 def query(self): 

324 return { 

325 "track_total_hits": True, 

326 "query": { 

327 "bool": { 

328 "must": [ 

329 {"terms": {"admin.application_status.exact": self.statuses}} 

330 ] 

331 } 

332 } 

333 } 

334 

335 

336class CurrentJournalQuery(object): 

337 

338 def __init__(self, journal_id, size=1): 

339 self.journal_id = journal_id 

340 self.size = size 

341 

342 def query(self): 

343 return { 

344 "track_total_hits": True, 

345 "query": { 

346 "bool": { 

347 "must": [ 

348 {"term": {"admin.current_journal.exact": self.journal_id}} 

349 ] 

350 } 

351 }, 

352 "sort": [ 

353 {"created_date": {"order": "desc"}} 

354 ], 

355 "size": self.size 

356 } 

357 

358 

359class RelatedJournalQuery(object): 

360 

361 def __init__(self, journal_id, size=1): 

362 self.journal_id = journal_id 

363 self.size = size 

364 

365 def query(self): 

366 return { 

367 "track_total_hits": True, 

368 "query": { 

369 "bool": { 

370 "must": [ 

371 {"term": {"admin.related_journal.exact": self.journal_id}} 

372 ] 

373 } 

374 }, 

375 "sort": [ 

376 {"created_date": {"order": "asc"}} 

377 ], 

378 "size": self.size 

379 } 

380 

381 

382class AssignedEditorGroupsQuery(object): 

383 """ 

384 ~~->$AssignedEditorGroups:Query~~ 

385 ~~^->Elasticsearch:Technology~~ 

386 """ 

387 

388 def __init__(self, editor_groups, application_type_name="new application"): 

389 self.editor_groups = editor_groups 

390 self.application_type_name = application_type_name 

391 

392 def query(self): 

393 return { 

394 "query": { 

395 "bool": { 

396 "must": [ 

397 {"terms": {"admin.editor_group.exact": self.editor_groups}}, 

398 {"term": {"index.application_type.exact": self.application_type_name}} 

399 ] 

400 } 

401 }, 

402 "size": 0, 

403 "aggs": { 

404 "editor_groups": { 

405 "terms": { 

406 "field": "admin.editor_group.exact", 

407 "size": len(self.editor_groups) or 1, 

408 "order": {"_term": "asc"} 

409 } 

410 } 

411 } 

412 }