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

1import json 

2from copy import deepcopy 

3 

4from flask_login import current_user 

5 

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 

12 

13 

14def journal_bulk_delete_manage(selection_query, dry_run=True): 

15 

16 estimates = JournalBulkDeleteBackgroundTask.estimate_delete_counts(selection_query) 

17 

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"]}) 

21 

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) 

29 

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"]}) 

34 

35 

36# ~~JournalBulkDelete:Task~~ 

37class JournalBulkDeleteBackgroundTask(AdminBackgroundTask): 

38 

39 __action__ = "journal_bulk_delete" 

40 

41 @classmethod 

42 def _job_parameter_check(cls, params): 

43 # we definitely need "ids" defined 

44 return bool(cls.get_param(params, 'ids')) 

45 

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 

53 

54 ids = self.get_param(params, 'ids') 

55 

56 if not self._job_parameter_check(params): 

57 raise BackgroundException("{}.run run without sufficient parameters".format(self.__class__.__name__)) 

58 

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'])) 

63 

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) 

80 

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))) 

84 

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 

94 

95 lock.batch_unlock('journal', ids, username) 

96 

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} 

103 

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] 

110 

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 

116 

117 :param kwargs: arbitrary keyword arguments pertaining to this task type 

118 :return: a BackgroundJob instance representing this task 

119 """ 

120 

121 super(JournalBulkDeleteBackgroundTask, cls).prepare(username, **kwargs) 

122 

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'])} 

128 

129 params = {} 

130 cls.set_param(params, 'ids', kwargs['ids']) 

131 

132 if not cls._job_parameter_check(params): 

133 raise BackgroundException("{}.prepare run without sufficient parameters".format(cls.__name__)) 

134 

135 job.params = params 

136 job.queue_id = huey_helper.queue_id 

137 

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)) 

141 

142 return job 

143 

144 @classmethod 

145 def submit(cls, background_job): 

146 """ 

147 Submit the specified BackgroundJob to the background queue 

148 

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)) 

154 

155 

156huey_helper = JournalBulkDeleteBackgroundTask.create_huey_helper(queue) 

157 

158 

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)