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
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-19 16:52 +0100
1from copy import deepcopy
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
10class Suggestion(JournalLikeObject):
11 __type__ = "suggestion"
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)
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
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())
34 @classmethod
35 def list_by_status(cls, status):
36 q = StatusQuery(status)
37 return cls.iterate(q=q.query())
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
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())
52 def mappings(self):
53 return es_data_mapping.create_mapping(self.get_struct(), MAPPING_OPTS)
55 @property
56 def current_journal(self):
57 return self._get_single("admin.current_journal")
59 def set_current_journal(self, journal_id):
60 self._set_with_struct("admin.current_journal", journal_id)
62 def remove_current_journal(self):
63 self._delete("admin.current_journal")
65 @property
66 def related_journal(self):
67 return self._get_single("admin.related_journal")
69 def set_related_journal(self, journal_id):
70 self._set_with_struct("admin.related_journal", journal_id)
72 def remove_related_journal(self):
73 self._delete("admin.related_journal")
75 @property
76 def application_status(self):
77 return self._get_single("admin.application_status")
79 def set_application_status(self, val):
80 self._set_with_struct("admin.application_status", val)
82 ### suggestion properties (as in, the Suggestion model's "suggestion" object ###
84 @property
85 def suggested_on(self):
86 return self._get_single("suggestion.suggested_on")
88 @suggested_on.setter
89 def suggested_on(self, val):
90 self._set_with_struct("suggestion.suggested_on", val)
92 @property
93 def articles_last_year(self):
94 return self._get_single("suggestion.articles_last_year")
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)
100 @property
101 def article_metadata(self):
102 return self._get_single("suggestion.article_metadata")
104 @article_metadata.setter
105 def article_metadata(self, val):
106 self._set_with_struct("suggestion.article_metadata", val)
108 @property
109 def suggester(self):
110 return self._get_single("suggestion.suggester", default={})
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)
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)
125 def _generate_index(self):
126 super(Suggestion, self)._generate_index()
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)
135 def prep(self):
136 self._generate_index()
137 self.set_last_updated()
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)
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 ],
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}
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}
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>"}}
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 = []
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)
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
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
311 def query(self):
312 return self._query
314class StatusQuery(object):
316 def __init__(self, statuses):
317 if not isinstance(statuses, list):
318 statuses = [statuses]
319 self.statuses = statuses
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 }
333class CurrentJournalQuery(object):
335 def __init__(self, journal_id, size=1):
336 self.journal_id = journal_id
337 self.size = size
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 }
355class RelatedJournalQuery(object):
357 def __init__(self, journal_id, size=1):
358 self.journal_id = journal_id
359 self.size = size
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 }