Coverage for portality/tasks/journal_bulk_delete.py: 95%
93 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 copy import deepcopy
2import json
3from datetime import datetime
5from flask_login import current_user
7from portality import models, lock
8from portality.bll import DOAJ
9from portality.core import app
11from portality.tasks.redis_huey import main_queue
12from portality.decorators import write_required
14from portality.background import AdminBackgroundTask, BackgroundApi, BackgroundException, BackgroundSummary
15from portality.ui.messages import Messages
18def journal_bulk_delete_manage(selection_query, dry_run=True):
20 estimates = JournalBulkDeleteBackgroundTask.estimate_delete_counts(selection_query)
22 if dry_run:
23 JournalBulkDeleteBackgroundTask.check_admin_privilege(current_user.id)
24 return BackgroundSummary(None, affected={"journals" : estimates["journals-to-be-deleted"], "articles" : estimates["articles-to-be-deleted"]})
26 ids = JournalBulkDeleteBackgroundTask.resolve_selection_query(selection_query)
27 job = JournalBulkDeleteBackgroundTask.prepare(
28 current_user.id,
29 selection_query=selection_query,
30 ids=ids
31 )
32 JournalBulkDeleteBackgroundTask.submit(job)
34 job_id = None
35 if job is not None:
36 job_id = job.id
37 return BackgroundSummary(job_id, affected={"journals" : estimates["journals-to-be-deleted"], "articles" : estimates["articles-to-be-deleted"]})
40class JournalBulkDeleteBackgroundTask(AdminBackgroundTask):
42 __action__ = "journal_bulk_delete"
44 @classmethod
45 def _job_parameter_check(cls, params):
46 # we definitely need "ids" defined
47 return bool(cls.get_param(params, 'ids'))
49 def run(self):
50 """
51 Execute the task as specified by the background_job
52 :return:
53 """
54 job = self.background_job
55 params = job.params
57 ids = self.get_param(params, 'ids')
59 if not self._job_parameter_check(params):
60 raise BackgroundException("{}.run run without sufficient parameters".format(self.__class__.__name__))
62 # repeat the estimations and log what they were at the time the job ran, in addition to what the user saw
63 # when requesting the job in journal_bulk_delete_manage
64 estimates = self.estimate_delete_counts(json.loads(job.reference['selection_query']))
65 job.add_audit_message(Messages.BULK_JOURNAL_DELETE.format(journal_no=estimates['journals-to-be-deleted'], article_no=estimates['articles-to-be-deleted']))
67 # Rejecting associated update request
68 # (we do this after deleting the journal, so that the journal is not updated by the rejection method)
69 # ~~->Account:Model~~
70 account = models.Account.pull(job.user)
71 # ~~->Application:Service~~
72 svc = DOAJ.applicationService()
73 date_after = datetime.utcnow()
74 urs_ids = svc.reject_update_request_of_journals(ids, account)
75 if len(urs_ids) > 0:
76 job.add_audit_message(Messages.AUTOMATICALLY_REJECTED_UPDATE_REQUEST_WITH_ID.format(urid=urs_ids))
77 else:
78 job.add_audit_message(Messages.NO_UPDATE_REQUESTS)
79 blocklist = []
80 for urid in urs_ids:
81 ur = models.Application.pull(urid)
82 blocklist.append((urid, ur.last_updated))
83 models.Application.blockall(blocklist)
85 journal_delete_q_by_ids = models.Journal.make_query(should_terms={'_id': ids}, consistent_order=False)
86 models.Journal.delete_selected(query=journal_delete_q_by_ids, articles=True, snapshot_journals=True, snapshot_articles=True)
87 job.add_audit_message(Messages.BULK_JOURNAL_DELETE_COMPLETED.format(journal_no=len(ids)))
89 def cleanup(self):
90 """
91 Cleanup after a successful OR failed run of the task
92 :return:
93 """
94 job = self.background_job
95 params = job.params
96 ids = self.get_param(params, 'ids')
97 username = job.user
99 lock.batch_unlock('journal', ids, username)
101 @classmethod
102 def estimate_delete_counts(cls, selection_query):
103 jtotal = models.Journal.hit_count(selection_query, consistent_order=False)
104 issns = models.Journal.issns_by_query(selection_query)
105 atotal = models.Article.count_by_issns(issns)
106 return {'journals-to-be-deleted': jtotal, 'articles-to-be-deleted': atotal}
108 @classmethod
109 def resolve_selection_query(cls, selection_query):
110 q = deepcopy(selection_query)
111 q["_source"] = False
112 iterator = models.Journal.iterate(q=q, page_size=5000, wrap=False)
113 return [j['_id'] for j in iterator]
115 @classmethod
116 def prepare(cls, username, **kwargs):
117 """
118 Take an arbitrary set of keyword arguments and return an instance of a BackgroundJob,
119 or fail with a suitable exception
121 :param kwargs: arbitrary keyword arguments pertaining to this task type
122 :return: a BackgroundJob instance representing this task
123 """
125 super(JournalBulkDeleteBackgroundTask, cls).prepare(username, **kwargs)
127 # first prepare a job record
128 job = models.BackgroundJob()
129 job.user = username
130 job.action = cls.__action__
131 job.reference = {'selection_query': json.dumps(kwargs['selection_query'])}
133 params = {}
134 cls.set_param(params, 'ids', kwargs['ids'])
136 if not cls._job_parameter_check(params):
137 raise BackgroundException("{}.prepare run without sufficient parameters".format(cls.__name__))
139 job.params = params
141 # now ensure that we have the locks for all the records, if they are lockable
142 # will raise an exception if this fails
143 lock.batch_lock('journal', kwargs['ids'], username, timeout=app.config.get("BACKGROUND_TASK_LOCK_TIMEOUT", 3600))
145 return job
147 @classmethod
148 def submit(cls, background_job):
149 """
150 Submit the specified BackgroundJob to the background queue
152 :param background_job: the BackgroundJob instance
153 :return:
154 """
155 background_job.save(blocking=True)
156 journal_bulk_delete.schedule(args=(background_job.id,), delay=10)
159@main_queue.task()
160@write_required(script=True)
161def journal_bulk_delete(job_id):
162 job = models.BackgroundJob.pull(job_id)
163 task = JournalBulkDeleteBackgroundTask(job)
164 BackgroundApi.execute(task)