Coverage for portality/view/admin.py: 34%

528 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-11-09 15:10 +0000

1import json 

2 

3from flask import Blueprint, request, flash, abort, make_response 

4from flask import render_template, redirect, url_for 

5from flask_login import current_user, login_required 

6from werkzeug.datastructures import MultiDict 

7 

8from portality import dao 

9import portality.models as models 

10from portality import constants 

11from portality import lock 

12from portality.background import BackgroundSummary 

13from portality.bll import DOAJ, exceptions 

14from portality.bll.exceptions import ArticleMergeConflict, DuplicateArticleException 

15from portality.core import app 

16from portality.crosswalks.application_form import ApplicationFormXWalk 

17from portality.decorators import ssl_required, restrict_to_role, write_required 

18from portality.forms.application_forms import ApplicationFormFactory, application_statuses 

19from portality.forms.application_forms import JournalFormFactory 

20from portality.forms.article_forms import ArticleFormFactory 

21from portality.lcc import lcc_jstree 

22from portality.lib.query_filters import remove_search_limits, update_request, not_update_request 

23from portality.tasks import journal_in_out_doaj, journal_bulk_edit, suggestion_bulk_edit, journal_bulk_delete, \ 

24 article_bulk_delete 

25from portality.ui.messages import Messages 

26from portality.util import flash_with_url, jsonp, make_json_resp, get_web_json_payload, validate_json 

27from portality.view.forms import EditorGroupForm, MakeContinuation 

28 

29from portality.bll.services.query import Query 

30 

31# ~~Admin:Blueprint~~ 

32blueprint = Blueprint('admin', __name__) 

33 

34 

35# restrict everything in admin to logged in users with the "admin" role 

36@blueprint.before_request 

37def restrict(): 

38 return restrict_to_role('admin') 

39 

40 

41# build an admin page where things can be done 

42@blueprint.route('/') 

43@login_required 

44@ssl_required 

45def index(): 

46 return render_template('admin/index.html', admin_page=True) 

47 

48 

49@blueprint.route("/journals", methods=["GET"]) 

50@login_required 

51@ssl_required 

52def journals(): 

53 qs = request.query_string 

54 target = url_for("admin.index") 

55 if qs: 

56 target += "?" + qs.decode() 

57 return redirect(target) 

58 

59 

60@blueprint.route("/journals", methods=["POST", "DELETE"]) 

61@login_required 

62@ssl_required 

63@write_required() 

64@jsonp 

65def journals_list(): 

66 if request.method == "POST": 

67 try: 

68 query = json.loads(request.values.get("q")) 

69 except: 

70 app.logger.warn("Bad Request at admin/journals: " + str(request.values.get("q"))) 

71 abort(400) 

72 

73 # get the total number of journals to be affected 

74 jtotal = models.Journal.hit_count(query, consistent_order=False) 

75 

76 # get the total number of articles to be affected 

77 issns = models.Journal.issns_by_query(query) 

78 atotal = models.Article.count_by_issns(issns) 

79 

80 resp = make_response(json.dumps({"journals" : jtotal, "articles" : atotal})) 

81 resp.mimetype = "application/json" 

82 return resp 

83 

84 elif request.method == "DELETE": 

85 if not current_user.has_role("delete_article"): 

86 abort(401) 

87 

88 try: 

89 query = json.loads(request.data) 

90 except: 

91 app.logger.warn("Bad Request at admin/journals: " + str(request.data)) 

92 abort(400) 

93 

94 # get only the query part 

95 query = {"query" : query.get("query")} 

96 models.Journal.delete_selected(query=query, articles=True, snapshot_journals=True, snapshot_articles=True) 

97 resp = make_response(json.dumps({"status" : "success"})) 

98 resp.mimetype = "application/json" 

99 return resp 

100 

101 

102@blueprint.route("/articles", methods=["POST", "DELETE"]) 

103@login_required 

104@ssl_required 

105@write_required() 

106@jsonp 

107def articles_list(): 

108 if request.method == "POST": 

109 try: 

110 query = json.loads(request.values.get("q")) 

111 except: 

112 print(request.values.get("q")) 

113 abort(400) 

114 total = models.Article.hit_count(query, consistent_order=False) 

115 resp = make_response(json.dumps({"total" : total})) 

116 resp.mimetype = "application/json" 

117 return resp 

118 elif request.method == "DELETE": 

119 if not current_user.has_role("delete_article"): 

120 abort(401) 

121 

122 try: 

123 query = json.loads(request.data) 

124 except: 

125 app.logger.warn("Bad Request at admin/journals: " + str(request.data)) 

126 abort(400) 

127 

128 # get only the query part 

129 query = {"query" : query.get("query")} 

130 models.Article.delete_selected(query=query, snapshot=True) 

131 resp = make_response(json.dumps({"status" : "success"})) 

132 resp.mimetype = "application/json" 

133 return resp 

134 

135 

136@blueprint.route("/delete/article/<article_id>", methods=["POST"]) 

137@login_required 

138@ssl_required 

139@write_required() 

140def article_endpoint(article_id): 

141 if not current_user.has_role("delete_article"): 

142 abort(401) 

143 a = models.Article.pull(article_id) 

144 if a is None: 

145 abort(404) 

146 delete = request.values.get("delete", "false") 

147 if delete != "true": 

148 abort(400) 

149 a.snapshot() 

150 a.delete() 

151 # return a json response 

152 resp = make_response(json.dumps({"success" : True})) 

153 resp.mimetype = "application/json" 

154 return resp 

155 

156@blueprint.route("/article/<article_id>", methods=["GET", "POST"]) 

157@login_required 

158@ssl_required 

159@write_required() 

160def article_page(article_id): 

161 if not current_user.has_role("edit_article"): 

162 abort(401) 

163 ap = models.Article.pull(article_id) 

164 if ap is None: 

165 abort(404) 

166 

167 fc = ArticleFormFactory.get_from_context(role="admin", source=ap, user=current_user) 

168 if request.method == "GET": 

169 return fc.render_template() 

170 

171 elif request.method == "POST": 

172 user = current_user._get_current_object() 

173 fc = ArticleFormFactory.get_from_context(role="admin", source=ap, user=user, form_data=request.form) 

174 

175 fc.modify_authors_if_required(request.values) 

176 

177 if fc.validate(): 

178 try: 

179 fc.finalise() 

180 except ArticleMergeConflict: 

181 Messages.flash(Messages.ARTICLE_METADATA_MERGE_CONFLICT) 

182 except DuplicateArticleException: 

183 Messages.flash(Messages.ARTICLE_METADATA_UPDATE_CONFLICT) 

184 

185 return fc.render_template() 

186 

187 

188@blueprint.route("/journal/<journal_id>", methods=["GET", "POST"]) 

189@login_required 

190@ssl_required 

191@write_required() 

192def journal_page(journal_id): 

193 # ~~JournalForm:Page~~ 

194 auth_svc = DOAJ.authorisationService() 

195 journal_svc = DOAJ.journalService() 

196 

197 journal, _ = journal_svc.journal(journal_id) 

198 if journal is None: 

199 abort(404) 

200 

201 try: 

202 auth_svc.can_edit_journal(current_user._get_current_object(), journal) 

203 except exceptions.AuthoriseException: 

204 abort(401) 

205 

206 # attempt to get a lock on the object 

207 try: 

208 lockinfo = lock.lock(constants.LOCK_JOURNAL, journal_id, current_user.id) 

209 except lock.Locked as l: 

210 return render_template("admin/journal_locked.html", journal=journal, lock=l.lock) 

211 

212 fc = JournalFormFactory.context("admin") 

213 if request.method == "GET": 

214 job = None 

215 job_id = request.values.get("job") 

216 if job_id is not None and job_id != "": 

217 # ~~-> BackgroundJobs:Model~~ 

218 job = models.BackgroundJob.pull(job_id) 

219 # ~~-> BackgroundJobs:Page~~ 

220 url = url_for("admin.background_jobs_search") + "?source=" + dao.Facetview2.url_encode_query(dao.Facetview2.make_query(job_id)) 

221 Messages.flash_with_url(Messages.ADMIN__WITHDRAW_REINSTATE.format(url=url), "success") 

222 fc.processor(source=journal) 

223 return fc.render_template(lock=lockinfo, job=job, obj=journal, lcc_tree=lcc_jstree) 

224 

225 elif request.method == "POST": 

226 processor = fc.processor(formdata=request.form, source=journal) 

227 if processor.validate(): 

228 try: 

229 processor.finalise() 

230 flash('Journal updated.', 'success') 

231 for a in processor.alert: 

232 flash_with_url(a, "success") 

233 return redirect(url_for("admin.journal_page", journal_id=journal.id, _anchor='done')) 

234 except Exception as e: 

235 flash(str(e)) 

236 return redirect(url_for("admin.journal_page", journal_id=journal.id, _anchor='cannot_edit')) 

237 else: 

238 return fc.render_template(lock=lockinfo, obj=journal, lcc_tree=lcc_jstree) 

239 

240###################################################### 

241# Endpoints for reinstating/withdrawing journals from the DOAJ 

242# 

243 

244@blueprint.route("/journal/<journal_id>/activate", methods=["GET", "POST"]) 

245@login_required 

246@ssl_required 

247@write_required() 

248def journal_activate(journal_id): 

249 job = journal_in_out_doaj.change_in_doaj([journal_id], True) 

250 return redirect(url_for('.journal_page', journal_id=journal_id, job=job.id)) 

251 

252 

253@blueprint.route("/journal/<journal_id>/deactivate", methods=["GET", "POST"]) 

254@login_required 

255@ssl_required 

256@write_required() 

257def journal_deactivate(journal_id): 

258 job = journal_in_out_doaj.change_in_doaj([journal_id], False) 

259 return redirect(url_for('.journal_page', journal_id=journal_id, job=job.id)) 

260 

261 

262@blueprint.route("/journals/bulk/withdraw", methods=["POST"]) 

263@login_required 

264@ssl_required 

265@write_required() 

266def journals_bulk_withdraw(): 

267 payload = get_web_json_payload() 

268 validate_json(payload, fields_must_be_present=['selection_query'], error_to_raise=BulkAdminEndpointException) 

269 

270 q = get_query_from_request(payload) 

271 summary = journal_in_out_doaj.change_by_query(q, False, dry_run=payload.get("dry_run", True)) 

272 return make_json_resp(summary.as_dict(), status_code=200) 

273 

274@blueprint.route("/journals/bulk/reinstate", methods=["POST"]) 

275@login_required 

276@ssl_required 

277@write_required() 

278def journals_bulk_reinstate(): 

279 payload = get_web_json_payload() 

280 validate_json(payload, fields_must_be_present=['selection_query'], error_to_raise=BulkAdminEndpointException) 

281 

282 q = get_query_from_request(payload) 

283 summary = journal_in_out_doaj.change_by_query(q, True, dry_run=payload.get("dry_run", True)) 

284 return make_json_resp(summary.as_dict(), status_code=200) 

285 

286# 

287##################################################################### 

288 

289@blueprint.route("/journal/<journal_id>/continue", methods=["GET", "POST"]) 

290@login_required 

291@ssl_required 

292@write_required() 

293def journal_continue(journal_id): 

294 j = models.Journal.pull(journal_id) 

295 if j is None: 

296 abort(404) 

297 

298 if request.method == "GET": 

299 type = request.values.get("type") 

300 form = MakeContinuation() 

301 form.type.data = type 

302 return render_template("admin/continuation.html", form=form, current=j) 

303 

304 elif request.method == "POST": 

305 form = MakeContinuation(request.form) 

306 if not form.validate(): 

307 return render_template('admin/continuation.html', form=form, current=j) 

308 

309 if form.type.data is None: 

310 abort(400) 

311 

312 if form.type.data not in ["replaces", "is_replaced_by"]: 

313 abort(400) 

314 

315 try: 

316 cont = j.make_continuation(form.type.data, eissn=form.eissn.data, pissn=form.pissn.data, title=form.title.data) 

317 except: 

318 abort(400) 

319 

320 flash("The continuation has been created (see below). You may now edit the other metadata associated with it. The original journal has also been updated with this continuation's ISSN(s). Once you are happy with this record, you can publish it to the DOAJ", "success") 

321 return redirect(url_for('.journal_page', journal_id=cont.id)) 

322 

323 

324@blueprint.route("/applications", methods=["GET"]) 

325@login_required 

326@ssl_required 

327def suggestions(): 

328 fc = ApplicationFormFactory.context("admin") 

329 return render_template("admin/applications.html", 

330 admin_page=True, 

331 application_status_choices=application_statuses(None, fc)) 

332 

333@blueprint.route("/update_requests", methods=["GET"]) 

334@login_required 

335@ssl_required 

336def update_requests(): 

337 fc = ApplicationFormFactory.context("admin") 

338 return render_template("admin/update_requests.html", 

339 admin_page=True, 

340 application_status_choices=application_statuses(None, fc)) 

341 

342 

343@blueprint.route("/application/<application_id>", methods=["GET", "POST"]) 

344@write_required() 

345@login_required 

346@ssl_required 

347def application(application_id): 

348 auth_svc = DOAJ.authorisationService() 

349 application_svc = DOAJ.applicationService() 

350 

351 ap, _ = application_svc.application(application_id) 

352 

353 if ap is None: 

354 abort(404) 

355 

356 try: 

357 auth_svc.can_edit_application(current_user._get_current_object(), ap) 

358 except exceptions.AuthoriseException: 

359 abort(401) 

360 

361 try: 

362 lockinfo = lock.lock(constants.LOCK_APPLICATION, application_id, current_user.id) 

363 except lock.Locked as l: 

364 return render_template("admin/application_locked.html", application=ap, lock=l.lock) 

365 

366 fc = ApplicationFormFactory.context("admin") 

367 form_diff, current_journal = ApplicationFormXWalk.update_request_diff(ap) 

368 

369 if request.method == "GET": 

370 fc.processor(source=ap) 

371 return fc.render_template(obj=ap, lock=lockinfo, form_diff=form_diff, 

372 current_journal=current_journal, lcc_tree=lcc_jstree) 

373 

374 elif request.method == "POST": 

375 processor = fc.processor(formdata=request.form, source=ap) 

376 if processor.validate(): 

377 try: 

378 processor.finalise(current_user._get_current_object()) 

379 # if (processor.form.resettedFields): 

380 # text = "Some fields has been resetted due to invalid value:" 

381 # for f in processor.form.resettedFields: 

382 # text += "<br>field: {}, invalid value: {}, new value: {}".format(f["name"], f["data"], f["default"]) 

383 # flash(text, 'info') 

384 flash('Application updated.', 'success') 

385 for a in processor.alert: 

386 flash_with_url(a, "success") 

387 return redirect(url_for("admin.application", application_id=ap.id, _anchor='done')) 

388 except Exception as e: 

389 flash(str(e)) 

390 return redirect(url_for("admin.application", application_id=ap.id, _anchor='cannot_edit')) 

391 else: 

392 return fc.render_template(obj=ap, lock=lockinfo, form_diff=form_diff, current_journal=current_journal, lcc_tree=lcc_jstree) 

393 

394 

395@blueprint.route("/application_quick_reject/<application_id>", methods=["POST"]) 

396@login_required 

397@ssl_required 

398@write_required() 

399def application_quick_reject(application_id): 

400 

401 # extract the note information from the request 

402 canned_reason = request.values.get("quick_reject", "") 

403 additional_info = request.values.get("quick_reject_details", "") 

404 reasons = [] 

405 if canned_reason != "": 

406 reasons.append(canned_reason) 

407 if additional_info != "": 

408 reasons.append(additional_info) 

409 if len(reasons) == 0: 

410 abort(400) 

411 reason = " - ".join(reasons) 

412 note = Messages.REJECT_NOTE_WRAPPER.format(editor=current_user.id, note=reason) 

413 

414 applicationService = DOAJ.applicationService() 

415 

416 # retrieve the application and an edit lock on that application 

417 application = None 

418 try: 

419 application, alock = applicationService.application(application_id, lock_application=True, lock_account=current_user._get_current_object()) 

420 except lock.Locked as e: 

421 abort(409) 

422 

423 # determine if this was a new application or an update request 

424 update_request = application.current_journal is not None 

425 if update_request: 

426 abort(400) 

427 

428 if application.owner is None: 

429 Messages.flash_with_url(Messages.ADMIN__QUICK_REJECT__NO_OWNER, "error") 

430 # redirect the user back to the edit page 

431 return redirect(url_for('.application', application_id=application_id)) 

432 

433 # reject the application 

434 old_status = application.application_status 

435 applicationService.reject_application(application, current_user._get_current_object(), note=note) 

436 

437 # send the notification email to the user 

438 if old_status != constants.APPLICATION_STATUS_REJECTED: 

439 eventsSvc = DOAJ.eventsService() 

440 eventsSvc.trigger(models.Event(constants.EVENT_APPLICATION_STATUS, current_user.id, { 

441 "application": application.data, 

442 "old_status": old_status, 

443 "new_status": constants.APPLICATION_STATUS_REJECTED, 

444 "process": constants.PROCESS__QUICK_REJECT, 

445 "note": reason 

446 })) 

447 

448 # sort out some flash messages for the user 

449 flash(note, "success") 

450 

451 msg = Messages.SENT_REJECTED_APPLICATION_EMAIL_TO_OWNER.format(user=application.owner) 

452 flash(msg, "success") 

453 

454 # redirect the user back to the edit page 

455 return redirect(url_for('.application', application_id=application_id)) 

456 

457 

458@blueprint.route("/admin_site_search", methods=["GET"]) 

459@login_required 

460@ssl_required 

461def admin_site_search(): 

462 #edit_formcontext = formcontext.ManEdBulkEdit() 

463 #edit_form = edit_formcontext.render_template() 

464 edit_formulaic_context = JournalFormFactory.context("bulk_edit") 

465 edit_form = edit_formulaic_context.render_template() 

466 

467 return render_template("admin/admin_site_search.html", 

468 admin_page=True, 

469 edit_form=edit_form) 

470 

471 

472@blueprint.route("/editor_groups") 

473@login_required 

474@ssl_required 

475def editor_group_search(): 

476 return render_template("admin/editor_group_search.html", admin_page=True) 

477 

478@blueprint.route("/background_jobs") 

479@login_required 

480@ssl_required 

481def background_jobs_search(): 

482 return render_template("admin/background_jobs_search.html", admin_page=True) 

483 

484@blueprint.route("/editor_group", methods=["GET", "POST"]) 

485@blueprint.route("/editor_group/<group_id>", methods=["GET", "POST"]) 

486@login_required 

487@ssl_required 

488@write_required() 

489def editor_group(group_id=None): 

490 if not current_user.has_role("modify_editor_groups"): 

491 abort(401) 

492 

493 # ~~->EditorGroup:Form~~ 

494 if request.method == "GET": 

495 form = EditorGroupForm() 

496 if group_id is not None: 

497 eg = models.EditorGroup.pull(group_id) 

498 form.group_id.data = eg.id 

499 form.name.data = eg.name 

500 form.maned.data = eg.maned 

501 form.editor.data = eg.editor 

502 form.associates.data = ",".join(eg.associates) 

503 return render_template("admin/editor_group.html", admin_page=True, form=form) 

504 

505 elif request.method == "POST": 

506 

507 if request.values.get("delete", "false") == "true": 

508 # we have been asked to delete the id 

509 if group_id is None: 

510 # we can only delete things that exist 

511 abort(400) 

512 eg = models.EditorGroup.pull(group_id) 

513 if eg is None: 

514 abort(404) 

515 

516 eg.delete() 

517 

518 # return a json response 

519 resp = make_response(json.dumps({"success" : True})) 

520 resp.mimetype = "application/json" 

521 return resp 

522 

523 # otherwise, we want to edit the content of the form or the object 

524 form = EditorGroupForm(request.form) 

525 

526 if form.validate(): 

527 # get the group id from the url or from the request parameters 

528 if group_id is None: 

529 group_id = request.values.get("group_id") 

530 group_id = group_id if group_id != "" else None 

531 

532 # if we have a group id, this is an edit, so get the existing group 

533 if group_id is not None: 

534 eg = models.EditorGroup.pull(group_id) 

535 if eg is None: 

536 abort(404) 

537 else: 

538 eg = models.EditorGroup() 

539 

540 associates = form.associates.data 

541 if associates is not None: 

542 associates = [a.strip() for a in associates.split(",") if a.strip() != ""] 

543 

544 # prep the user accounts with the correct role(s) 

545 ed = models.Account.pull(form.editor.data) 

546 ed.add_role("editor") 

547 ed.save() 

548 if associates is not None: 

549 for a in associates: 

550 ae = models.Account.pull(a) 

551 if ae is not None: # If the account has been deleted, pull fails 

552 ae.add_role("associate_editor") 

553 ae.save() 

554 

555 eg.set_name(form.name.data) 

556 eg.set_maned(form.maned.data) 

557 eg.set_editor(form.editor.data) 

558 if associates is not None: 

559 eg.set_associates(associates) 

560 eg.save() 

561 

562 flash("Group was updated - changes may not be reflected below immediately. Reload the page to see the update.", "success") 

563 return redirect(url_for('admin.editor_group_search')) 

564 else: 

565 return render_template("admin/editor_group.html", admin_page=True, form=form) 

566 

567 

568@blueprint.route("/autocomplete/user") 

569@login_required 

570@ssl_required 

571def user_autocomplete(): 

572 q = request.values.get("q") 

573 s = request.values.get("s", 10) 

574 ac = models.Account.autocomplete("id", q, size=s) 

575 

576 # return a json response 

577 resp = make_response(json.dumps(ac)) 

578 resp.mimetype = "application/json" 

579 return resp 

580 

581 

582# Route which returns the associate editor account names within a given editor group 

583@blueprint.route("/dropdown/eg_associates") 

584@login_required 

585@ssl_required 

586def eg_associates_dropdown(): 

587 egn = request.values.get("egn") 

588 eg = models.EditorGroup.pull_by_key("name", egn) 

589 

590 if eg is not None: 

591 editors = [eg.editor] 

592 editors += eg.associates 

593 editors = list(set(editors)) 

594 else: 

595 editors = None 

596 

597 # return a json response 

598 resp = make_response(json.dumps(editors)) 

599 resp.mimetype = "application/json" 

600 return resp 

601 

602 

603#################################################### 

604## endpoints for bulk edit 

605 

606class BulkAdminEndpointException(Exception): 

607 pass 

608 

609 

610@app.errorhandler(BulkAdminEndpointException) 

611def bulk_admin_endpoints_bad_request(exception): 

612 r = {} 

613 r['error'] = exception.message 

614 return make_json_resp(r, status_code=400) 

615 

616 

617def get_bulk_edit_background_task_manager(doaj_type): 

618 if doaj_type == 'journals': 

619 return journal_bulk_edit.journal_manage 

620 elif doaj_type in ['applications', 'update_requests']: 

621 return suggestion_bulk_edit.suggestion_manage 

622 else: 

623 raise BulkAdminEndpointException('Unsupported DOAJ type - you can currently only bulk edit journals and applications/update_requests.') 

624 

625 

626def get_query_from_request(payload, doaj_type=None): 

627 q = payload['selection_query'] 

628 q = remove_search_limits(q) 

629 

630 q = Query(q) 

631 

632 if doaj_type == "update_requests": 

633 update_request(q) 

634 elif doaj_type == "applications": 

635 not_update_request(q) 

636 

637 return q.as_dict() 

638 

639 

640@blueprint.route("/<doaj_type>/bulk/assign_editor_group", methods=["POST"]) 

641@login_required 

642@ssl_required 

643@write_required() 

644def bulk_assign_editor_group(doaj_type): 

645 task = get_bulk_edit_background_task_manager(doaj_type) 

646 

647 payload = get_web_json_payload() 

648 validate_json(payload, fields_must_be_present=['selection_query', 'editor_group'], error_to_raise=BulkAdminEndpointException) 

649 

650 summary = task( 

651 selection_query=get_query_from_request(payload, doaj_type), 

652 editor_group=payload['editor_group'], 

653 dry_run=payload.get('dry_run', True) 

654 ) 

655 

656 return make_json_resp(summary.as_dict(), status_code=200) 

657 

658 

659@blueprint.route("/<doaj_type>/bulk/add_note", methods=["POST"]) 

660@login_required 

661@ssl_required 

662@write_required() 

663def bulk_add_note(doaj_type): 

664 task = get_bulk_edit_background_task_manager(doaj_type) 

665 

666 payload = get_web_json_payload() 

667 validate_json(payload, fields_must_be_present=['selection_query', 'note'], error_to_raise=BulkAdminEndpointException) 

668 

669 summary = task( 

670 selection_query=get_query_from_request(payload, doaj_type), 

671 note=payload['note'], 

672 dry_run=payload.get('dry_run', True) 

673 ) 

674 

675 return make_json_resp(summary.as_dict(), status_code=200) 

676 

677@blueprint.route("/journals/bulk/edit_metadata", methods=["POST"]) 

678@login_required 

679@ssl_required 

680@write_required() 

681def bulk_edit_journal_metadata(): 

682 task = get_bulk_edit_background_task_manager("journals") 

683 

684 payload = get_web_json_payload() 

685 if not "metadata" in payload: 

686 raise BulkAdminEndpointException("key 'metadata' not present in request json") 

687 

688 formdata = MultiDict(payload["metadata"]) 

689 formulaic_context = JournalFormFactory.context("bulk_edit") 

690 fc = formulaic_context.processor(formdata=formdata) 

691 

692 if not fc.validate(): 

693 msg = "Unable to submit your request due to form validation issues: " 

694 for field in fc.form: 

695 if field.errors: 

696 msg += field.label.text + " - " + ",".join(field.errors) 

697 summary = BackgroundSummary(None, error=msg) 

698 else: 

699 summary = task( 

700 selection_query=get_query_from_request(payload), 

701 dry_run=payload.get('dry_run', True), 

702 **payload["metadata"] 

703 ) 

704 

705 return make_json_resp(summary.as_dict(), status_code=200) 

706 

707 

708@blueprint.route("/<doaj_type>/bulk/change_status", methods=["POST"]) 

709@login_required 

710@ssl_required 

711@write_required() 

712def applications_bulk_change_status(doaj_type): 

713 if doaj_type not in ["applications", "update_requests"]: 

714 abort(403) 

715 payload = get_web_json_payload() 

716 validate_json(payload, fields_must_be_present=['selection_query', 'application_status'], error_to_raise=BulkAdminEndpointException) 

717 

718 q = get_query_from_request(payload, doaj_type) 

719 summary = get_bulk_edit_background_task_manager('applications')( 

720 selection_query=q, 

721 application_status=payload['application_status'], 

722 dry_run=payload.get('dry_run', True) 

723 ) 

724 

725 return make_json_resp(summary.as_dict(), status_code=200) 

726 

727 

728@blueprint.route("/journals/bulk/delete", methods=['POST']) 

729@write_required() 

730def bulk_journals_delete(): 

731 if not current_user.has_role("ultra_bulk_delete"): 

732 abort(403) 

733 payload = get_web_json_payload() 

734 validate_json(payload, fields_must_be_present=['selection_query'], error_to_raise=BulkAdminEndpointException) 

735 

736 q = get_query_from_request(payload) 

737 summary = journal_bulk_delete.journal_bulk_delete_manage( 

738 selection_query=q, 

739 dry_run=payload.get('dry_run', True) 

740 ) 

741 return make_json_resp(summary.as_dict(), status_code=200) 

742 

743 

744@blueprint.route("/articles/bulk/delete", methods=['POST']) 

745@write_required() 

746def bulk_articles_delete(): 

747 if not current_user.has_role("ultra_bulk_delete"): 

748 abort(403) 

749 payload = get_web_json_payload() 

750 validate_json(payload, fields_must_be_present=['selection_query'], error_to_raise=BulkAdminEndpointException) 

751 

752 q = get_query_from_request(payload) 

753 summary = article_bulk_delete.article_bulk_delete_manage( 

754 selection_query=q, 

755 dry_run=payload.get('dry_run', True) 

756 ) 

757 

758 return make_json_resp(summary.as_dict(), status_code=200) 

759 

760#################################################