Coverage for portality/models/v1/suggestion.py: 0%

167 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-07-19 16:52 +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.v1 import shared_structs 

7from portality.models.v1.journal import JournalLikeObject 

8 

9 

10class Suggestion(JournalLikeObject): 

11 __type__ = "suggestion" 

12 

13 def __init__(self, **kwargs): 

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

15 if "_source" in kwargs: 

16 kwargs = kwargs["_source"] 

17 self._add_struct(shared_structs.SHARED_BIBJSON) 

18 self._add_struct(shared_structs.JOURNAL_BIBJSON_EXTENSION) 

19 self._add_struct(APPLICATION_STRUCT) 

20 super(Suggestion, self).__init__(raw=kwargs) 

21 

22 @classmethod 

23 def get_by_owner(cls, owner): 

24 q = SuggestionQuery(owner=owner) 

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

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

27 return records 

28 

29 @classmethod 

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

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

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

33 

34 @classmethod 

35 def list_by_status(cls, status): 

36 q = StatusQuery(status) 

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

38 

39 @classmethod 

40 def find_latest_by_current_journal(cls, journal_id): 

41 q = CurrentJournalQuery(journal_id) 

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

43 if len(results) > 0: 

44 return results[0] 

45 return None 

46 

47 @classmethod 

48 def find_all_by_related_journal(cls, journal_id): 

49 q = RelatedJournalQuery(journal_id, size=1000) 

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

51 

52 def mappings(self): 

53 return es_data_mapping.create_mapping(self.get_struct(), MAPPING_OPTS) 

54 

55 @property 

56 def current_journal(self): 

57 return self._get_single("admin.current_journal") 

58 

59 def set_current_journal(self, journal_id): 

60 self._set_with_struct("admin.current_journal", journal_id) 

61 

62 def remove_current_journal(self): 

63 self._delete("admin.current_journal") 

64 

65 @property 

66 def related_journal(self): 

67 return self._get_single("admin.related_journal") 

68 

69 def set_related_journal(self, journal_id): 

70 self._set_with_struct("admin.related_journal", journal_id) 

71 

72 def remove_related_journal(self): 

73 self._delete("admin.related_journal") 

74 

75 @property 

76 def application_status(self): 

77 return self._get_single("admin.application_status") 

78 

79 def set_application_status(self, val): 

80 self._set_with_struct("admin.application_status", val) 

81 

82 ### suggestion properties (as in, the Suggestion model's "suggestion" object ### 

83 

84 @property 

85 def suggested_on(self): 

86 return self._get_single("suggestion.suggested_on") 

87 

88 @suggested_on.setter 

89 def suggested_on(self, val): 

90 self._set_with_struct("suggestion.suggested_on", val) 

91 

92 @property 

93 def articles_last_year(self): 

94 return self._get_single("suggestion.articles_last_year") 

95 

96 def set_articles_last_year(self, count, url): 

97 self._set_with_struct("suggestion.articles_last_year.count", count) 

98 self._set_with_struct("suggestion.articles_last_year.url", url) 

99 

100 @property 

101 def article_metadata(self): 

102 return self._get_single("suggestion.article_metadata") 

103 

104 @article_metadata.setter 

105 def article_metadata(self, val): 

106 self._set_with_struct("suggestion.article_metadata", val) 

107 

108 @property 

109 def suggester(self): 

110 return self._get_single("suggestion.suggester", default={}) 

111 

112 def set_suggester(self, name, email): 

113 self._set_with_struct("suggestion.suggester.name", name) 

114 self._set_with_struct("suggestion.suggester.email", email) 

115 

116 def _sync_owner_to_journal(self): 

117 if self.current_journal is None: 

118 return 

119 from portality.models import Journal 

120 cj = Journal.pull(self.current_journal) 

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

122 cj.set_owner(self.owner) 

123 cj.save(sync_owner=False) 

124 

125 def _generate_index(self): 

126 super(Suggestion, self)._generate_index() 

127 

128 if self.current_journal is not None: 

129 self._set_with_struct("index.application_type", constants.INDEX_RECORD_TYPE_UPDATE_REQUEST_UNFINISHED) 

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

131 self._set_with_struct("index.application_type", constants.INDEX_RECORD_TYPE_NEW_APPLICATION_FINISHED) 

132 else: 

133 self._set_with_struct("index.application_type", constants.INDEX_RECORD_TYPE_NEW_APPLICATION_UNFINISHED) 

134 

135 def prep(self): 

136 self._generate_index() 

137 self.set_last_updated() 

138 

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

140 self.prep() 

141 self.check_construct() 

142 if sync_owner: 

143 self._sync_owner_to_journal() 

144 return super(Suggestion, self).save(**kwargs) 

145 

146APPLICATION_STRUCT = { 

147 "fields" : { 

148 "id" : {"coerce" : "unicode"}, 

149 "created_date" : {"coerce" : "utcdatetime"}, 

150 "last_updated" : {"coerce" : "utcdatetime"}, 

151 "last_manual_update" : {"coerce" : "utcdatetime"} 

152 }, 

153 "objects" : [ 

154 "admin", "index", "suggestion" 

155 ], 

156 

157 "structs" : { 

158 "admin" : { 

159 "fields" : { 

160 "seal" : {"coerce" : "bool"}, 

161 "bulk_upload" : {"coerce" : "unicode"}, 

162 "owner" : {"coerce" : "unicode"}, 

163 "editor_group" : {"coerce" : "unicode"}, 

164 "editor" : {"coerce" : "unicode"}, 

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

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

167 "application_status" : {"coerce" : "unicode"} 

168 }, 

169 "lists" : { 

170 "contact" : {"contains" : "object"}, 

171 "notes" : {"contains" : "object"} 

172 }, 

173 "structs" : { 

174 "contact" : { 

175 "fields" : { 

176 "email" : {"coerce" : "unicode"}, 

177 "name" : {"coerce" : "unicode"} 

178 } 

179 }, 

180 "notes" : { 

181 "fields" : { 

182 "note" : {"coerce" : "unicode"}, 

183 "date" : {"coerce" : "utcdatetime"} 

184 } 

185 } 

186 } 

187 }, 

188 "index" : { 

189 "fields" : { 

190 "country" : {"coerce" : "unicode"}, 

191 "homepage_url" : {"coerce" : "unicode"}, 

192 "waiver_policy_url" : {"coerce" : "unicode"}, 

193 "editorial_board_url" : {"coerce" : "unicode"}, 

194 "aims_scope_url" : {"coerce" : "unicode"}, 

195 "author_instructions_url" : {"coerce" : "unicode"}, 

196 "oa_statement_url" : {"coerce" : "unicode"}, 

197 "has_apc" : {"coerce" : "unicode"}, 

198 "has_seal" : {"coerce" : "unicode"}, 

199 "unpunctitle" : {"coerce" : "unicode"}, 

200 "asciiunpunctitle" : {"coerce" : "unicode"}, 

201 "continued" : {"coerce" : "unicode"}, 

202 "application_type": {"coerce": "unicode"}, 

203 "has_editor_group" : {"coerce" : "unicode"}, 

204 "has_editor" : {"coerce" : "unicode"} 

205 }, 

206 "lists" : { 

207 "issn" : {"contains" : "field", "coerce" : "unicode"}, 

208 "title" : {"contains" : "field", "coerce" : "unicode"}, 

209 "subject" : {"contains" : "field", "coerce" : "unicode"}, 

210 "schema_subject" : {"contains" : "field", "coerce" : "unicode"}, 

211 "classification" : {"contains" : "field", "coerce" : "unicode"}, 

212 "language" : {"contains" : "field", "coerce" : "unicode"}, 

213 "license" : {"contains" : "field", "coerce" : "unicode"}, 

214 "classification_paths" : {"contains" : "field", "coerce" : "unicode"}, 

215 "schema_code" : {"contains" : "field", "coerce" : "unicode"}, 

216 "publisher" : {"contains" : "field", "coerce" : "unicode"} 

217 } 

218 }, 

219 "suggestion" : { 

220 "fields" : { 

221 "article_metadata" : {"coerce" : "bool"}, 

222 "suggested_on" : {"coerce" : "utcdatetime"} 

223 }, 

224 "objects" : [ 

225 "articles_last_year", 

226 "suggester" 

227 ], 

228 "structs" : { 

229 "suggester" : { 

230 "fields" : { 

231 "name" : {"coerce" : "unicode"}, 

232 "email" : {"coerce" : "unicode"} 

233 } 

234 }, 

235 "articles_last_year" : { 

236 "fields" : { 

237 "count" : {"coerce" : "integer"}, 

238 "url" : {"coerce" : "unicode"} 

239 } 

240 } 

241 } 

242 } 

243 } 

244} 

245 

246MAPPING_OPTS = { 

247 "dynamic": None, 

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

249 "exceptions": { 

250 "admin.notes.note": { 

251 "type": "text", 

252 "index": False, 

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

254 } 

255 } 

256} 

257 

258 

259class SuggestionQuery(object): 

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

261 _email_term = {"term" : {"suggestion.suggester.email.exact" : "<email address>"}} 

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

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

264 

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

266 self.email = email 

267 self.owner = owner 

268 if statuses: 

269 self.statuses = statuses 

270 else: 

271 self.statuses = [] 

272 

273 def query(self): 

274 q = deepcopy(self._base_query) 

275 if self.email: 

276 et = deepcopy(self._email_term) 

277 et["term"]["suggestion.suggester.email.exact"] = self.email 

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

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

280 st = deepcopy(self._status_terms) 

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

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

283 

284 if self.owner is not None: 

285 ot = deepcopy(self._owner_term) 

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

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

288 return q 

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 def __init__(self, owner, statuses, size=10): 

304 self._query = deepcopy(self.base_query) 

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

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

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

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

309 self._query["size"] = size 

310 

311 def query(self): 

312 return self._query 

313 

314class StatusQuery(object): 

315 

316 def __init__(self, statuses): 

317 if not isinstance(statuses, list): 

318 statuses = [statuses] 

319 self.statuses = statuses 

320 

321 def query(self): 

322 return { 

323 "track_total_hits": True, 

324 "query" : { 

325 "bool" : { 

326 "must" : [ 

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

328 ] 

329 } 

330 } 

331 } 

332 

333class CurrentJournalQuery(object): 

334 

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

336 self.journal_id = journal_id 

337 self.size = size 

338 

339 def query(self): 

340 return { 

341 "track_total_hits": True, 

342 "query" : { 

343 "bool" : { 

344 "must" : [ 

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

346 ] 

347 } 

348 }, 

349 "sort" : [ 

350 {"created_date" : {"order" : "desc"}} 

351 ], 

352 "size" : self.size 

353 } 

354 

355class RelatedJournalQuery(object): 

356 

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

358 self.journal_id = journal_id 

359 self.size = size 

360 

361 def query(self): 

362 return { 

363 "track_total_hits": True, 

364 "query" : { 

365 "bool" : { 

366 "must" : [ 

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

368 ] 

369 } 

370 }, 

371 "sort" : [ 

372 {"created_date" : {"order" : "asc"}} 

373 ], 

374 "size" : self.size 

375 }