Coverage for portality / tasks / journal_bulk_delete.py: 95%
91 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-04 09:41 +0100
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-04 09:41 +0100
1import json
2from copy import deepcopy
4from flask_login import current_user
6from portality import models, lock
7from portality.background import AdminBackgroundTask, BackgroundApi, BackgroundException, BackgroundSummary
8from portality.bll import DOAJ
9from portality.core import app
10from portality.tasks.redis_huey import events_queue as queue
11from portality.ui.messages import Messages
14def journal_bulk_delete_manage(selection_query, dry_run=True):
16 estimates = JournalBulkDeleteBackgroundTask.estimate_delete_counts(selection_query)
18 if dry_run:
19 JournalBulkDeleteBackgroundTask.check_admin_privilege(current_user.id)
20 return BackgroundSummary(None, affected={"journals" : estimates["journals-to-be-deleted"], "articles" : estimates["articles-to-be-deleted"]})
22 ids = JournalBulkDeleteBackgroundTask.resolve_selection_query(selection_query)
23 job = JournalBulkDeleteBackgroundTask.prepare(
24 current_user.id,
25 selection_query=selection_query,
26 ids=ids
27 )
28 JournalBulkDeleteBackgroundTask.submit(job)
30 job_id = None
31 if job is not None:
32 job_id = job.id
33 return BackgroundSummary(job_id, affected={"journals": estimates["journals-to-be-deleted"], "articles": estimates["articles-to-be-deleted"]})
36# ~~JournalBulkDelete:Task~~
37class JournalBulkDeleteBackgroundTask(AdminBackgroundTask):
39 __action__ = "journal_bulk_delete"
41 @classmethod
42 def _job_parameter_check(cls, params):
43 # we definitely need "ids" defined
44 return bool(cls.get_param(params, 'ids'))
46 def run(self):
47 """
48 Execute the task as specified by the background_job
49 :return:
50 """
51 job = self.background_job
52 params = job.params
54 ids = self.get_param(params, 'ids')
56 if not self._job_parameter_check(params):
57 raise BackgroundException("{}.run run without sufficient parameters".format(self.__class__.__name__))
59 # repeat the estimations and log what they were at the time the job ran, in addition to what the user saw
60 # when requesting the job in journal_bulk_delete_manage
61 estimates = self.estimate_delete_counts(json.loads(job.reference['selection_query']))
62 job.add_audit_message(Messages.BULK_JOURNAL_DELETE.format(journal_no=estimates['journals-to-be-deleted'], article_no=estimates['articles-to-be-deleted']))
64 # Rejecting associated update request
65 # (we do this after deleting the journal, so that the journal is not updated by the rejection method)
66 # ~~->Account:Model~~
67 account = models.Account.pull(job.user)
68 # ~~->Application:Service~~
69 svc = DOAJ.applicationService()
70 urs_ids = svc.reject_update_request_of_journals(ids, account)
71 if len(urs_ids) > 0:
72 job.add_audit_message(Messages.AUTOMATICALLY_REJECTED_UPDATE_REQUEST_WITH_ID.format(urid=urs_ids))
73 else:
74 job.add_audit_message(Messages.NO_UPDATE_REQUESTS)
75 blocklist = []
76 for urid in urs_ids:
77 ur = models.Application.pull(urid)
78 blocklist.append((urid, ur.last_updated))
79 models.Application.blockall(blocklist)
81 journal_delete_q_by_ids = models.Journal.make_query(should_terms={'_id': ids}, consistent_order=False)
82 models.Journal.delete_selected(query=journal_delete_q_by_ids, articles=True, snapshot_journals=True, snapshot_articles=True)
83 job.add_audit_message(Messages.BULK_JOURNAL_DELETE_COMPLETED.format(journal_no=len(ids)))
85 def cleanup(self):
86 """
87 Cleanup after a successful OR failed run of the task
88 :return:
89 """
90 job = self.background_job
91 params = job.params
92 ids = self.get_param(params, 'ids')
93 username = job.user
95 lock.batch_unlock('journal', ids, username)
97 @classmethod
98 def estimate_delete_counts(cls, selection_query):
99 jtotal = models.Journal.hit_count(selection_query, consistent_order=False)
100 issns = models.Journal.issns_by_query(selection_query)
101 atotal = models.Article.count_by_issns(issns)
102 return {'journals-to-be-deleted': jtotal, 'articles-to-be-deleted': atotal}
104 @classmethod
105 def resolve_selection_query(cls, selection_query):
106 q = deepcopy(selection_query)
107 q["_source"] = False
108 iterator = models.Journal.iterate(q=q, page_size=5000, wrap=False)
109 return [j['_id'] for j in iterator]
111 @classmethod
112 def prepare(cls, username, **kwargs):
113 """
114 Take an arbitrary set of keyword arguments and return an instance of a BackgroundJob,
115 or fail with a suitable exception
117 :param kwargs: arbitrary keyword arguments pertaining to this task type
118 :return: a BackgroundJob instance representing this task
119 """
121 super(JournalBulkDeleteBackgroundTask, cls).prepare(username, **kwargs)
123 # first prepare a job record
124 job = models.BackgroundJob()
125 job.user = username
126 job.action = cls.__action__
127 job.reference = {'selection_query': json.dumps(kwargs['selection_query'])}
129 params = {}
130 cls.set_param(params, 'ids', kwargs['ids'])
132 if not cls._job_parameter_check(params):
133 raise BackgroundException("{}.prepare run without sufficient parameters".format(cls.__name__))
135 job.params = params
136 job.queue_id = huey_helper.queue_id
138 # now ensure that we have the locks for all the records, if they are lockable
139 # will raise an exception if this fails
140 lock.batch_lock('journal', kwargs['ids'], username, timeout=app.config.get("BACKGROUND_TASK_LOCK_TIMEOUT", 3600))
142 return job
144 @classmethod
145 def submit(cls, background_job):
146 """
147 Submit the specified BackgroundJob to the background queue
149 :param background_job: the BackgroundJob instance
150 :return:
151 """
152 background_job.save(blocking=True)
153 journal_bulk_delete.schedule(args=(background_job.id,), delay=app.config.get('HUEY_ASYNC_DELAY', 10))
156huey_helper = JournalBulkDeleteBackgroundTask.create_huey_helper(queue)
159@huey_helper.register_execute(is_load_config=False)
160def journal_bulk_delete(job_id):
161 job = models.BackgroundJob.pull(job_id)
162 task = JournalBulkDeleteBackgroundTask(job)
163 BackgroundApi.execute(task)