def test_02_get_application(self, name, application, application_id, account, lock_application, raises=None): if lock_application: lock.lock(constants.LOCK_APPLICATION, application.id, "someoneelse", blocking=True) svc = DOAJ.applicationService() if application is not None: application.save(blocking=True) if raises is not None: with self.assertRaises(raises): if account is None: retrieved, _ = svc.application(application_id) else: retrieved, jlock = svc.application(application_id, lock_application=True, lock_account=account) else: if account is None: retrieved, _ = svc.application(application_id) if retrieved is not None: assert retrieved.data == application.data else: assert retrieved is None else: retrieved, jlock = svc.application(application_id, lock_application=True, lock_account=account) if retrieved is not None: assert retrieved.data == application.data else: assert retrieved is None time.sleep(2) assert lock.has_lock(constants.LOCK_APPLICATION, application_id, account.id)
def test_01_get_journal(self, name, journal, journal_id, account, lock_journal, raises=None): if lock_journal: lock.lock("journal", journal.id, "someoneelse", blocking=True) svc = DOAJ.journalService() if journal is not None: journal.save(blocking=True) if raises is not None: with self.assertRaises(raises): if account is None: retrieved, _ = svc.journal(journal_id) else: retrieved, jlock = svc.journal(journal_id, lock_journal=True, lock_account=account) else: if account is None: retrieved, _ = svc.journal(journal_id) if retrieved is not None: assert retrieved.data == journal.data else: assert retrieved is None else: retrieved, jlock = svc.journal(journal_id, lock_journal=True, lock_account=account) if retrieved is not None: assert retrieved.data == journal.data else: assert retrieved is None time.sleep(2) assert lock.has_lock("journal", journal_id, account.id)
def application(self, application_id, lock_application=False, lock_account=None, lock_timeout=None): """ Function to retrieve an application by its id :param application_id: the id of the application :param: lock_application: should we lock the resource on retrieval :param: lock_account: which account is doing the locking? Must be present if lock_journal=True :param: lock_timeout: how long to lock the resource for. May be none, in which case it will default :return: Tuple of (Suggestion Object, Lock Object) """ # first validate the incoming arguments to ensure that we've got the right thing argvalidate("application", [ {"arg": application_id, "allow_none" : False, "arg_name" : "application_id"}, {"arg": lock_application, "instance" : bool, "allow_none" : False, "arg_name" : "lock_journal"}, {"arg": lock_account, "instance" : models.Account, "allow_none" : True, "arg_name" : "lock_account"}, {"arg": lock_timeout, "instance" : int, "allow_none" : True, "arg_name" : "lock_timeout"} ], exceptions.ArgumentException) # pull the application from the database application = models.Suggestion.pull(application_id) # if we've retrieved the journal, and a lock is requested, request it the_lock = None if application is not None and lock_application: if lock_account is not None: the_lock = lock.lock(constants.LOCK_APPLICATION, application_id, lock_account.id, lock_timeout) else: raise exceptions.ArgumentException("If you specify lock_application on application retrieval, you must also provide lock_account") return application, the_lock
def journal(self, journal_id, lock_journal=False, lock_account=None, lock_timeout=None): """ Function to retrieve a journal by its id, and to optionally lock the resource May raise a Locked exception, if a lock is requested but can't be obtained. :param journal_id: the id of the journal :param: lock_journal: should we lock the resource on retrieval :param: lock_account: which account is doing the locking? Must be present if lock_journal=True :param: lock_timeout: how long to lock the resource for. May be none, in which case it will default :return: Tuple of (Journal Object, Lock Object) """ # first validate the incoming arguments to ensure that we've got the right thing argvalidate("journal", [ {"arg": journal_id, "allow_none" : False, "arg_name" : "journal_id"}, {"arg": lock_journal, "instance" : bool, "allow_none" : False, "arg_name" : "lock_journal"}, {"arg": lock_account, "instance" : models.Account, "allow_none" : True, "arg_name" : "lock_account"}, {"arg": lock_timeout, "instance" : int, "allow_none" : True, "arg_name" : "lock_timeout"} ], exceptions.ArgumentException) # retrieve the journal journal = models.Journal.pull(journal_id) # if we've retrieved the journal, and a lock is requested, request it the_lock = None if journal is not None and lock_journal: if lock_account is not None: the_lock = lock.lock("journal", journal_id, lock_account.id, lock_timeout) else: raise exceptions.ArgumentException("If you specify lock_journal on journal retrieval, you must also provide lock_account") return journal, the_lock
def application(self, application_id, lock_application=False, lock_account=None, lock_timeout=None): """ Function to retrieve an application by its id :param application_id: the id of the application :param: lock_application: should we lock the resource on retrieval :param: lock_account: which account is doing the locking? Must be present if lock_journal=True :param: lock_timeout: how long to lock the resource for. May be none, in which case it will default :return: Tuple of (Suggestion Object, Lock Object) """ # first validate the incoming arguments to ensure that we've got the right thing argvalidate("application", [{ "arg": application_id, "allow_none": False, "arg_name": "application_id" }, { "arg": lock_application, "instance": bool, "allow_none": False, "arg_name": "lock_journal" }, { "arg": lock_account, "instance": models.Account, "allow_none": True, "arg_name": "lock_account" }, { "arg": lock_timeout, "instance": int, "allow_none": True, "arg_name": "lock_timeout" }], exceptions.ArgumentException) # pull the application from the database application = models.Suggestion.pull(application_id) # if we've retrieved the journal, and a lock is requested, request it the_lock = None if application is not None and lock_application: if lock_account is not None: the_lock = lock.lock(constants.LOCK_APPLICATION, application_id, lock_account.id, lock_timeout) else: raise exceptions.ArgumentException( "If you specify lock_application on application retrieval, you must also provide lock_account" ) return application, the_lock
def test_04_timeout(self): source = JournalFixtureFactory.make_journal_source() j = models.Journal(**source) j.save() time.sleep(2) after = datetime.utcnow() + timedelta(seconds=2300) # set a lock with a longer timout l = lock.lock("journal", j.id, "testuser", 2400) assert dates.parse(l.expires) > after
def journal_page(journal_id): # user must have the role "edit_journal" if not current_user.has_role("edit_journal"): abort(401) # get the journal, so we can check our permissions against it j = models.Journal.pull(journal_id) if j is None: abort(404) # user must be either the "admin.editor" of the journal, or the editor of the "admin.editor_group" # is the user the currently assigned editor of the journal? passed = False if j.editor == current_user.id: passed = True # now check whether the user is the editor of the editor group role = "associate_editor" eg = models.EditorGroup.pull_by_key("name", j.editor_group) if eg is not None and eg.editor == current_user.id: passed = True role = "editor" # if the user wasn't the editor or the owner of the editor group, unauthorised if not passed: abort(401) # attempt to get a lock on the object try: lockinfo = lock.lock("journal", journal_id, current_user.id) except lock.Locked as l: return render_template("editor/journal_locked.html", journal=j, lock=l.lock, edit_journal_page=True) if request.method == "GET": fc = formcontext.JournalFormFactory.get_form_context(role=role, source=j) return fc.render_template(edit_journal_page=True, lock=lockinfo) elif request.method == "POST": fc = formcontext.JournalFormFactory.get_form_context(role=role, form_data=request.form, source=j) if fc.validate(): try: fc.finalise() flash('Journal updated.', 'success') for a in fc.alert: flash_with_url(a, "success") return redirect(url_for("editor.journal_page", journal_id=j.id, _anchor='done')) except formcontext.FormContextException as e: flash(e.message) return redirect(url_for("editor.journal_page", journal_id=j.id, _anchor='cannot_edit')) else: return fc.render_template(edit_journal_page=True, lock=lockinfo)
def journal_page(journal_id): # user must have the role "edit_journal" if not current_user.has_role("edit_journal"): abort(401) # get the journal, so we can check our permissions against it j = models.Journal.pull(journal_id) if j is None: abort(404) # user must be either the "admin.editor" of the journal, or the editor of the "admin.editor_group" # is the user the currently assigned editor of the journal? passed = False if j.editor == current_user.id: passed = True # now check whether the user is the editor of the editor group role = "associate_editor" eg = models.EditorGroup.pull_by_key("name", j.editor_group) if eg is not None and eg.editor == current_user.id: passed = True role = "editor" # if the user wasn't the editor or the owner of the editor group, unauthorised if not passed: abort(401) # attempt to get a lock on the object try: lockinfo = lock.lock("journal", journal_id, current_user.id) except lock.Locked as l: return render_template("editor/journal_locked.html", journal=j, lock=l.lock, edit_journal_page=True) if request.method == "GET": fc = formcontext.JournalFormFactory.get_form_context(role=role, source=j) return fc.render_template(edit_journal_page=True, lock=lockinfo) elif request.method == "POST": fc = formcontext.JournalFormFactory.get_form_context(role=role, form_data=request.form, source=j) if fc.validate(): try: fc.finalise() flash('Journal updated.', 'success') for a in fc.alert: flash_with_url(a, "success") return redirect(url_for("editor.journal_page", journal_id=j.id, _anchor='done')) except formcontext.FormContextException as e: flash(str(e)) return redirect(url_for("editor.journal_page", journal_id=j.id, _anchor='cannot_edit')) else: return fc.render_template(edit_journal_page=True, lock=lockinfo)
def test_03_batch_lock_unlock(self): source = JournalFixtureFactory.make_journal_source() ids = [] # create a bunch of journals that we can play with j = models.Journal(**deepcopy(source)) j.save() ids.append(j.id) j = models.Journal(**deepcopy(source)) j.save() ids.append(j.id) j = models.Journal(**deepcopy(source)) j.save() ids.append(j.id) j = models.Journal(**deepcopy(source)) j.save() ids.append(j.id) j = models.Journal(**deepcopy(source)) j.save() ids.append(j.id) time.sleep(2) ls = lock.batch_lock("journal", ids, "testuser") assert len(ls) == 5 time.sleep(2) report = lock.batch_unlock("journal", ids, "testuser") assert len(report["success"]) == 5 assert len(report["fail"]) == 0 time.sleep(2) # now lock an individual record by a different user and check that no locks are set # in batch l = lock.lock("journal", ids[3], "otheruser") time.sleep(2) with self.assertRaises(lock.Locked): ls = lock.batch_lock("journal", ids, "testuser") for id in ids: assert lock.has_lock("journal", id, "testuser") is False
def test_01_lock_success_fail(self): source = JournalFixtureFactory.make_journal_source() j = models.Journal(**source) j.save() time.sleep(2) # first set a lock l = lock.lock("journal", j.id, "testuser") assert l.about == j.id assert l.type == "journal" assert l.username == "testuser" assert not l.is_expired() time.sleep(2) # now try and set the lock again for the same thing by the same user l2 = lock.lock("journal", j.id, "testuser") assert l2 is not None assert l2.id == l.id # now try and set the lock as another user with self.assertRaises(lock.Locked): l3 = lock.lock("journal", j.id, "otheruser")
def journal_page(journal_id): if not current_user.has_role("edit_journal"): abort(401) ap = models.Journal.pull(journal_id) if ap is None: abort(404) # attempt to get a lock on the object try: lockinfo = lock.lock("journal", journal_id, current_user.id) except lock.Locked as l: return render_template("admin/journal_locked.html", journal=ap, lock=l.lock, edit_journal_page=True) if request.method == "GET": job = None job_id = request.values.get("job") if job_id is not None and job_id != "": job = models.BackgroundJob.pull(job_id) fc = formcontext.JournalFormFactory.get_form_context(role="admin", source=ap) return fc.render_template(edit_journal_page=True, lock=lockinfo, job=job) elif request.method == "POST": fc = formcontext.JournalFormFactory.get_form_context( role="admin", form_data=request.form, source=ap) if fc.validate(): try: fc.finalise() flash('Journal updated.', 'success') for a in fc.alert: flash_with_url(a, "success") return redirect( url_for("admin.journal_page", journal_id=ap.id, _anchor='done')) except formcontext.FormContextException as e: flash(str(e)) return redirect( url_for("admin.journal_page", journal_id=ap.id, _anchor='cannot_edit')) else: return fc.render_template(edit_journal_page=True, lock=lockinfo)
def suggestion_page(suggestion_id): if not current_user.has_role("edit_suggestion"): abort(401) ap = models.Suggestion.pull(suggestion_id) if ap is None: abort(404) # attempt to get a lock on the object try: lockinfo = lock.lock("suggestion", suggestion_id, current_user.id) except lock.Locked as l: return render_template("admin/suggestion_locked.html", suggestion=ap, lock=l.lock, edit_suggestion_page=True) if request.method == "GET": fc = formcontext.ApplicationFormFactory.get_form_context(role="admin", source=ap) return fc.render_template(edit_suggestion_page=True, lock=lockinfo) elif request.method == "POST": fc = formcontext.ApplicationFormFactory.get_form_context( role="admin", form_data=request.form, source=ap) if fc.validate(): try: fc.finalise() flash('Application updated.', 'success') for a in fc.alert: flash_with_url(a, "success") return redirect( url_for("admin.suggestion_page", suggestion_id=ap.id, _anchor='done')) except formcontext.FormContextException as e: flash(str(e)) return redirect( url_for("admin.suggestion_page", suggestion_id=ap.id, _anchor='cannot_edit')) else: return fc.render_template(edit_suggestion_page=True, lock=lockinfo)
def test_02_unlock(self): unlocked = lock.unlock("journal", "qojoiwjreiqwefoijqwiefjw", "testuser") assert unlocked is True source = JournalFixtureFactory.make_journal_source() j = models.Journal(**source) j.save() time.sleep(2) # first set a lock l = lock.lock("journal", j.id, "testuser") time.sleep(2) # try and unlock as a different user unlocked = lock.unlock("journal", j.id, "otheruser") assert unlocked is False # now unlock for real unlocked = lock.unlock("journal", j.id, "testuser") assert unlocked is True
def request_handler(request, journal_id, redirect_route="admin.journal_page", template="admin/journal.html", locked_template="admin/journal_locked.html", activate_deactivate=False, group_editable=False, editors=None, editorial_available=False): # check our permissions if not current_user.has_role("edit_journal"): abort(401) j = get_journal(journal_id) if j is None: abort(404) # attempt to get a lock on the object try: lockinfo = lock.lock("journal", journal_id, current_user.id) except lock.Locked as l: return render_template(locked_template, journal=j, lock=l.lock, edit_journal_page=True) current_info = models.ObjectDict(JournalFormXWalk.obj2form(j)) form = JournalForm(request.form, current_info) current_country = xwalk.get_country_code(j.bibjson().country) if current_country not in country_options_two_char_code_index: # couldn't find it, better warn the user to look for it # themselves country_help_text = "This journal's country has been recorded as \"{country}\". Please select it in the Country menu.".format(country=current_country) else: country_help_text = '' form.country.description = '<span class="red">' + country_help_text + '</span>' # add the contents of a few fields to their descriptions since select2 autocomplete # would otherwise obscure the full values if form.publisher.data: if not form.publisher.description: form.publisher.description = 'Full contents: ' + form.publisher.data else: form.publisher.description += '<br><br>Full contents: ' + form.publisher.data if form.society_institution.data: if not form.society_institution.description: form.society_institution.description = 'Full contents: ' + form.society_institution.data else: form.society_institution.description += '<br><br>Full contents: ' + form.society_institution.data if form.platform.data: if not form.platform.description: form.platform.description = 'Full contents: ' + form.platform.data else: form.platform.description += '<br><br>Full contents: ' + form.platform.data first_field_with_error = '' if editors is not None: form.editor.choices = [("", "Choose an editor")] + [(editor, editor) for editor in editors] else: if j.editor is not None: form.editor.choices = [(j.editor, j.editor)] else: form.editor.choices = [("", "")] if request.method == 'POST': if form.make_all_fields_optional.data: valid = True else: valid = form.validate() if valid: # even though you can only edit journals right now, keeping the same # method as editing suggestions (i.e. creating a new object # and editing its properties) email_editor = False if group_editable: email_editor = JournalFormXWalk.is_new_editor_group(form, j) email_associate = False if editorial_available: email_associate = JournalFormXWalk.is_new_editor(form, j) # do the core crosswalk journal = JournalFormXWalk.form2obj(form, existing_journal=j) # some of the properties (id, in_doaj, etc.) have to be carried over # otherwise they implicitly end up getting changed to their defaults # when a journal gets edited (e.g. it always gets taken out of DOAJ) # if we don't copy over the in_doaj attribute to the new journal object journal['id'] = j['id'] created_date = j.created_date if j.created_date else datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ") journal.set_created(created_date) journal.bibjson().active = j.is_in_doaj() journal.set_in_doaj(j.is_in_doaj()) if ((journal.owner is None or journal.owner == "") and (j.owner is not None)) or not current_user.has_role("admin"): journal.set_owner(j.owner) if not group_editable or not editorial_available: journal.set_editor_group(j.editor_group) if not editorial_available: journal.set_editor(j.editor) # FIXME: probably should check that the editor is in the editor_group and remove if not journal.save() flash('Journal updated.', 'success') # only actually send the email when we've successfully processed the form if email_editor: send_editor_group_email(journal) if email_associate: send_editor_email(journal) return redirect(url_for(redirect_route, journal_id=journal_id, _anchor='done')) # meaningless anchor to replace #first_problem used on the form # anchors persist between 3xx redirects to the same resource else: for field in form: # in order of definition of fields, so the order of rendering should be (manually) kept the same as the order of definition for this to work if field.errors: first_field_with_error = field.short_name break return render_template( template, form=form, first_field_with_error=first_field_with_error, q_numbers=xrange(1, 10000).__iter__(), # a generator for the purpose of displaying numbered questions other_val=other_val, digital_archiving_policy_specific_library_value=digital_archiving_policy_specific_library_value, edit_journal_page=True, admin_page=True, journal=j, subjectstr=subjects2str(j.bibjson().subjects()), lcc_jstree=json.dumps(lcc_jstree), activate_deactivate=activate_deactivate, group_editable=group_editable, editorial_available=editorial_available, lock=lockinfo )
def request_handler(request, journal_id, redirect_route="admin.journal_page", template="admin/journal.html", locked_template="admin/journal_locked.html", activate_deactivate=False, group_editable=False, editors=None, editorial_available=False): # check our permissions if not current_user.has_role("edit_journal"): abort(401) j = get_journal(journal_id) if j is None: abort(404) # attempt to get a lock on the object try: lockinfo = lock.lock("journal", journal_id, current_user.id) except lock.Locked as l: return render_template(locked_template, journal=j, lock=l.lock, edit_journal_page=True) current_info = models.ObjectDict(JournalFormXWalk.obj2form(j)) form = JournalForm(request.form, current_info) current_country = xwalk.get_country_code(j.bibjson().country) if current_country not in country_options_two_char_code_index: # couldn't find it, better warn the user to look for it # themselves country_help_text = "This journal's country has been recorded as \"{country}\". Please select it in the Country menu.".format( country=current_country) else: country_help_text = '' form.country.description = '<span class="red">' + country_help_text + '</span>' # add the contents of a few fields to their descriptions since select2 autocomplete # would otherwise obscure the full values if form.publisher.data: if not form.publisher.description: form.publisher.description = 'Full contents: ' + form.publisher.data else: form.publisher.description += '<br><br>Full contents: ' + form.publisher.data if form.society_institution.data: if not form.society_institution.description: form.society_institution.description = 'Full contents: ' + form.society_institution.data else: form.society_institution.description += '<br><br>Full contents: ' + form.society_institution.data if form.platform.data: if not form.platform.description: form.platform.description = 'Full contents: ' + form.platform.data else: form.platform.description += '<br><br>Full contents: ' + form.platform.data first_field_with_error = '' if editors is not None: form.editor.choices = [("", "Choose an editor") ] + [(editor, editor) for editor in editors] else: if j.editor is not None: form.editor.choices = [(j.editor, j.editor)] else: form.editor.choices = [("", "")] if request.method == 'POST': if form.make_all_fields_optional.data: valid = True else: valid = form.validate() if valid: # even though you can only edit journals right now, keeping the same # method as editing suggestions (i.e. creating a new object # and editing its properties) email_editor = False if group_editable: email_editor = JournalFormXWalk.is_new_editor_group(form, j) email_associate = False if editorial_available: email_associate = JournalFormXWalk.is_new_editor(form, j) # do the core crosswalk journal = JournalFormXWalk.form2obj(form, existing_journal=j) # some of the properties (id, in_doaj, etc.) have to be carried over # otherwise they implicitly end up getting changed to their defaults # when a journal gets edited (e.g. it always gets taken out of DOAJ) # if we don't copy over the in_doaj attribute to the new journal object journal['id'] = j['id'] created_date = j.created_date if j.created_date else datetime.now( ).strftime("%Y-%m-%dT%H:%M:%SZ") journal.set_created(created_date) journal.bibjson().active = j.is_in_doaj() journal.set_in_doaj(j.is_in_doaj()) if ((journal.owner is None or journal.owner == "") and (j.owner is not None)) or not current_user.has_role("admin"): journal.set_owner(j.owner) if not group_editable or not editorial_available: journal.set_editor_group(j.editor_group) if not editorial_available: journal.set_editor(j.editor) # FIXME: probably should check that the editor is in the editor_group and remove if not journal.save() flash('Journal updated.', 'success') # only actually send the email when we've successfully processed the form if email_editor: send_editor_group_email(journal) if email_associate: send_editor_email(journal) return redirect( url_for(redirect_route, journal_id=journal_id, _anchor='done')) # meaningless anchor to replace #first_problem used on the form # anchors persist between 3xx redirects to the same resource else: for field in form: # in order of definition of fields, so the order of rendering should be (manually) kept the same as the order of definition for this to work if field.errors: first_field_with_error = field.short_name break return render_template( template, form=form, first_field_with_error=first_field_with_error, q_numbers=xrange(1, 10000).__iter__( ), # a generator for the purpose of displaying numbered questions other_val=other_val, digital_archiving_policy_specific_library_value= digital_archiving_policy_specific_library_value, edit_journal_page=True, admin_page=True, journal=j, subjectstr=subjects2str(j.bibjson().subjects()), lcc_jstree=json.dumps(lcc_jstree), activate_deactivate=activate_deactivate, group_editable=group_editable, editorial_available=editorial_available, lock=lockinfo)
def update_request_for_journal(self, journal_id, account=None, lock_timeout=None): """ Obtain an update request application object for the journal with the given journal_id An update request may either be loaded from the database, if it already exists, or created in-memory if it has not previously been created. If an account is provided, this will validate that the account holder is allowed to make the conversion from journal to application, if a conversion is required. When this request runs, the journal will be locked to the provided account if an account is given. If the application is loaded from the database, this will also be locked for the account holder. :param journal_id: :param account: :return: a tuple of (Application Object, Journal Lock, Application Lock) """ # first validate the incoming arguments to ensure that we've got the right thing argvalidate("update_request_for_journal", [ {"arg": journal_id, "instance" : basestring, "allow_none" : False, "arg_name" : "journal_id"}, {"arg" : account, "instance" : models.Account, "allow_none" : True, "arg_name" : "account"}, {"arg" : lock_timeout, "instance" : int, "allow_none" : True, "arg_name" : "lock_timeout"} ], exceptions.ArgumentException) if app.logger.isEnabledFor("debug"): app.logger.debug("Entering update_request_for_journal") journalService = DOAJ.journalService() authService = DOAJ.authorisationService() # first retrieve the journal, and return empty if there isn't one. # We don't attempt to obtain a lock at this stage, as we want to check that the user is authorised first journal_lock = None journal, _ = journalService.journal(journal_id) if journal is None: app.logger.info("Request for journal {x} did not find anything in the database".format(x=journal_id)) return None, None, None # if the journal is not in_doaj, we won't create an update request for it if not journal.is_in_doaj(): app.logger.info("Request for journal {x} found it is not in_doaj; will not create update request".format(x=journal_id)) return None, None, None # retrieve the latest application attached to this journal application_lock = None application = models.Suggestion.find_latest_by_current_journal(journal_id) # if no such application exists, create one in memory (this will check that the user is permitted to create one) # at the same time, create the lock for the journal. This will throw an AuthorisedException or a Locked exception # (in that order of preference) if any problems arise. if application is None: app.logger.info("No existing update request for journal {x}; creating one".format(x=journal.id)) application = journalService.journal_2_application(journal, account=account) lra_id = journal.latest_related_application_id() if lra_id is not None: lra, _ = self.application(lra_id) if lra is not None: self.patch_application(application, lra) if account is not None: journal_lock = lock.lock("journal", journal_id, account.id) # otherwise check that the user (if given) has the rights to edit the application # then lock the application and journal to the account. # If a lock cannot be obtained, unlock the journal and application before we return elif account is not None: try: authService.can_edit_application(account, application) application_lock = lock.lock("suggestion", application.id, account.id) journal_lock = lock.lock("journal", journal_id, account.id) except lock.Locked as e: if application_lock is not None: application_lock.delete() if journal_lock is not None: journal_lock.delete() raise except exceptions.AuthoriseException as e: msg = "Account {x} is not permitted to edit the current update request on journal {y}".format(x=account.id, y=journal.id) app.logger.info(msg) e.message = msg raise app.logger.info("Using existing application {y} as update request for journal {x}".format(y=application.id, x=journal.id)) if app.logger.isEnabledFor("debug"): app.logger.debug("Completed update_request_for_journal; return application object") return application, journal_lock, application_lock
def delete_application(self, application_id, account): """ Function to delete an application, and all references to it in other objects (current and related journals) The application and all related journals need to be locked before this process can proceed, so you may get a lock.Locked exception :param application_id: :param account: :return: """ # first validate the incoming arguments to ensure that we've got the right thing argvalidate("delete_application", [ {"arg": application_id, "instance" : unicode, "allow_none" : False, "arg_name" : "application_id"}, {"arg" : account, "instance" : models.Account, "allow_none" : False, "arg_name" : "account"} ], exceptions.ArgumentException) journalService = DOAJ.journalService() authService = DOAJ.authorisationService() # get hold of a copy of the application. If there isn't one, our work here is done # (note the application could be locked, in which case this will raise a lock.Locked exception) # get the application application, _ = self.application(application_id) if application is None: raise exceptions.NoSuchObjectException # determine if the user can edit the application authService.can_edit_application(account, application) # attempt to lock the record (this may raise a Locked exception) alock = lock.lock(constants.LOCK_APPLICATION, application_id, account.id) # obtain the current journal, with associated lock current_journal = None cjlock = None if application.current_journal is not None: try: current_journal, cjlock = journalService.journal(application.current_journal, lock_journal=True, lock_account=account) except lock.Locked as e: # if the resource is locked, we have to back out if alock is not None: alock.delete() raise # obtain the related journal, with associated lock related_journal = None rjlock = None if application.related_journal is not None: try: related_journal, rjlock = journalService.journal(application.related_journal, lock_journal=True, lock_account=account) except lock.Locked as e: # if the resource is locked, we have to back out if alock is not None: alock.delete() if cjlock is not None: cjlock.delete() raise try: if current_journal is not None: current_journal.remove_current_application() saved = current_journal.save() if saved is None: raise exceptions.SaveException("Unable to save journal record") if related_journal is not None: relation_record = related_journal.related_application_record(application_id) if relation_record is None: relation_record = {} related_journal.add_related_application(application_id, relation_record.get("date_accepted"), "deleted") saved = related_journal.save() if saved is None: raise exceptions.SaveException("Unable to save journal record") application.delete() finally: if alock is not None: alock.delete() if cjlock is not None: cjlock.delete() if rjlock is not None: rjlock.delete() return
def test_01_delete_application(self, name, application_type, account_type, current_journal, related_journal, raises): ############################################### ## set up # create the test application (if needed), and the associated current_journal and related_journal in suitable states application = None cj = None rj = None if application_type == "found" or application_type == "locked": application = Suggestion( **ApplicationFixtureFactory.make_application_source()) if current_journal == "none": application.remove_current_journal() elif current_journal == "not_found": application.set_current_journal("123456789987654321") elif current_journal == "found": cj = Journal(**JournalFixtureFactory.make_journal_source()) cj.set_id(cj.makeid()) cj.save(blocking=True) application.set_current_journal(cj.id) elif current_journal == "locked": cj = Journal(**JournalFixtureFactory.make_journal_source()) cj.set_id(cj.makeid()) cj.save(blocking=True) application.set_current_journal(cj.id) lock.lock(constants.LOCK_JOURNAL, cj.id, "otheruser") if related_journal == "none": application.remove_related_journal() elif related_journal == "not_found": application.set_related_journal("123456789987654321") elif related_journal == "found": rj = Journal(**JournalFixtureFactory.make_journal_source()) rj.set_id(rj.makeid()) rj.save(blocking=True) application.set_related_journal(rj.id) elif related_journal == "locked": rj = Journal(**JournalFixtureFactory.make_journal_source()) rj.set_id(rj.makeid()) rj.save(blocking=True) application.set_related_journal(rj.id) lock.lock(constants.LOCK_JOURNAL, rj.id, "otheruser") acc = None if account_type != "none": acc = Account(**AccountFixtureFactory.make_publisher_source()) if account_type == "not_permitted": acc.remove_role("publisher") if application_type == "locked": thelock = lock.lock(constants.LOCK_APPLICATION, application.id, "otheruser") # we can't explicitly block on the lock, but we can halt until we confirm it is saved thelock.blockall([(thelock.id, thelock.last_updated)]) application_id = None if application is not None: if acc is not None: application.set_owner(acc.id) application.save(blocking=True) application_id = application.id elif application_type == "not_found": application_id = "sdjfasofwefkwflkajdfasjd" ########################################################### # Execution svc = DOAJ.applicationService() if raises != "": with self.assertRaises(EXCEPTIONS[raises]): svc.delete_application(application_id, acc) time.sleep(1) check_locks(application, cj, rj, acc) else: svc.delete_application(application_id, acc) # we need to sleep, so the index catches up time.sleep(1) # check that no locks remain set for this user check_locks(application, cj, rj, acc) # check that the application actually is gone if application is not None: assert Suggestion.pull(application.id) is None # check that the current journal no longer has a reference to the application if cj is not None: cj = Journal.pull(cj.id) assert cj.current_application is None # check that the related journal has a record that the application was deleted if rj is not None: rj = Journal.pull(rj.id) record = rj.related_application_record(application.id) assert "status" in record assert record["status"] == "deleted"
def delete_application(self, application_id, account): """ Function to delete an application, and all references to it in other objects (current and related journals) The application and all related journals need to be locked before this process can proceed, so you may get a lock.Locked exception :param application_id: :param account: :return: """ # first validate the incoming arguments to ensure that we've got the right thing argvalidate("delete_application", [{ "arg": application_id, "instance": unicode, "allow_none": False, "arg_name": "application_id" }, { "arg": account, "instance": models.Account, "allow_none": False, "arg_name": "account" }], exceptions.ArgumentException) journalService = DOAJ.journalService() authService = DOAJ.authorisationService() # get hold of a copy of the application. If there isn't one, our work here is done # (note the application could be locked, in which case this will raise a lock.Locked exception) # get the application application, _ = self.application(application_id) if application is None: raise exceptions.NoSuchObjectException # determine if the user can edit the application authService.can_edit_application(account, application) # attempt to lock the record (this may raise a Locked exception) alock = lock.lock(constants.LOCK_APPLICATION, application_id, account.id) # obtain the current journal, with associated lock current_journal = None cjlock = None if application.current_journal is not None: try: current_journal, cjlock = journalService.journal( application.current_journal, lock_journal=True, lock_account=account) except lock.Locked as e: # if the resource is locked, we have to back out if alock is not None: alock.delete() raise # obtain the related journal, with associated lock related_journal = None rjlock = None if application.related_journal is not None: try: related_journal, rjlock = journalService.journal( application.related_journal, lock_journal=True, lock_account=account) except lock.Locked as e: # if the resource is locked, we have to back out if alock is not None: alock.delete() if cjlock is not None: cjlock.delete() raise try: if current_journal is not None: current_journal.remove_current_application() saved = current_journal.save() if saved is None: raise exceptions.SaveException( "Unable to save journal record") if related_journal is not None: relation_record = related_journal.related_application_record( application_id) if relation_record is None: relation_record = {} related_journal.add_related_application( application_id, relation_record.get("date_accepted"), "deleted") saved = related_journal.save() if saved is None: raise exceptions.SaveException( "Unable to save journal record") application.delete() finally: if alock is not None: alock.delete() if cjlock is not None: cjlock.delete() if rjlock is not None: rjlock.delete() return
def test_01_delete_application(self, name, application_type, account_type, current_journal, related_journal, raises): ############################################### ## set up # create the test application (if needed), and the associated current_journal and related_journal in suitable states application = None cj = None rj = None if application_type == "found" or application_type == "locked": application = Suggestion(**ApplicationFixtureFactory.make_application_source()) if current_journal == "none": application.remove_current_journal() elif current_journal == "not_found": application.set_current_journal("123456789987654321") elif current_journal == "found": cj = Journal(**JournalFixtureFactory.make_journal_source()) cj.set_id(cj.makeid()) cj.save(blocking=True) application.set_current_journal(cj.id) elif current_journal == "locked": cj = Journal(**JournalFixtureFactory.make_journal_source()) cj.set_id(cj.makeid()) cj.save(blocking=True) application.set_current_journal(cj.id) lock.lock(constants.LOCK_JOURNAL, cj.id, "otheruser") if related_journal == "none": application.remove_related_journal() elif related_journal == "not_found": application.set_related_journal("123456789987654321") elif related_journal == "found": rj = Journal(**JournalFixtureFactory.make_journal_source()) rj.set_id(rj.makeid()) rj.save(blocking=True) application.set_related_journal(rj.id) elif related_journal == "locked": rj = Journal(**JournalFixtureFactory.make_journal_source()) rj.set_id(rj.makeid()) rj.save(blocking=True) application.set_related_journal(rj.id) lock.lock(constants.LOCK_JOURNAL, rj.id, "otheruser") acc = None if account_type != "none": acc = Account(**AccountFixtureFactory.make_publisher_source()) if account_type == "not_permitted": acc.remove_role("publisher") if application_type == "locked": thelock = lock.lock(constants.LOCK_APPLICATION, application.id, "otheruser") # we can't explicitly block on the lock, but we can halt until we confirm it is saved thelock.blockall([(thelock.id, thelock.last_updated)]) application_id = None if application is not None: if acc is not None: application.set_owner(acc.id) application.save(blocking=True) application_id = application.id elif application_type == "not_found": application_id = u"sdjfasofwefkwflkajdfasjd" ########################################################### # Execution svc = DOAJ.applicationService() if raises != "": with self.assertRaises(EXCEPTIONS[raises]): svc.delete_application(application_id, acc) time.sleep(1) check_locks(application, cj, rj, acc) else: svc.delete_application(application_id, acc) # we need to sleep, so the index catches up time.sleep(1) # check that no locks remain set for this user check_locks(application, cj, rj, acc) # check that the application actually is gone if application is not None: assert Suggestion.pull(application.id) is None # check that the current journal no longer has a reference to the application if cj is not None: cj = Journal.pull(cj.id) assert cj.current_application is None # check that the related journal has a record that the application was deleted if rj is not None: rj = Journal.pull(rj.id) record = rj.related_application_record(application.id) assert "status" in record assert record["status"] == "deleted"
def update_request_for_journal(self, journal_id, account=None, lock_timeout=None): """ Obtain an update request application object for the journal with the given journal_id An update request may either be loaded from the database, if it already exists, or created in-memory if it has not previously been created. If an account is provided, this will validate that the account holder is allowed to make the conversion from journal to application, if a conversion is required. When this request runs, the journal will be locked to the provided account if an account is given. If the application is loaded from the database, this will also be locked for the account holder. :param journal_id: :param account: :return: a tuple of (Application Object, Journal Lock, Application Lock) """ # first validate the incoming arguments to ensure that we've got the right thing argvalidate("update_request_for_journal", [{ "arg": journal_id, "instance": basestring, "allow_none": False, "arg_name": "journal_id" }, { "arg": account, "instance": models.Account, "allow_none": True, "arg_name": "account" }, { "arg": lock_timeout, "instance": int, "allow_none": True, "arg_name": "lock_timeout" }], exceptions.ArgumentException) if app.logger.isEnabledFor("debug"): app.logger.debug("Entering update_request_for_journal") journalService = DOAJ.journalService() authService = DOAJ.authorisationService() # first retrieve the journal, and return empty if there isn't one. # We don't attempt to obtain a lock at this stage, as we want to check that the user is authorised first journal_lock = None journal, _ = journalService.journal(journal_id) if journal is None: app.logger.info( "Request for journal {x} did not find anything in the database" .format(x=journal_id)) return None, None, None # if the journal is not in_doaj, we won't create an update request for it if not journal.is_in_doaj(): app.logger.info( "Request for journal {x} found it is not in_doaj; will not create update request" .format(x=journal_id)) return None, None, None # retrieve the latest application attached to this journal application_lock = None application = models.Suggestion.find_latest_by_current_journal( journal_id) # if no such application exists, create one in memory (this will check that the user is permitted to create one) # at the same time, create the lock for the journal. This will throw an AuthorisedException or a Locked exception # (in that order of preference) if any problems arise. if application is None: app.logger.info( "No existing update request for journal {x}; creating one". format(x=journal.id)) application = journalService.journal_2_application(journal, account=account) lra_id = journal.latest_related_application_id() if lra_id is not None: lra, _ = self.application(lra_id) if lra is not None: self.patch_application(application, lra) if account is not None: journal_lock = lock.lock("journal", journal_id, account.id) # otherwise check that the user (if given) has the rights to edit the application # then lock the application and journal to the account. # If a lock cannot be obtained, unlock the journal and application before we return elif account is not None: try: authService.can_edit_application(account, application) application_lock = lock.lock("suggestion", application.id, account.id) journal_lock = lock.lock("journal", journal_id, account.id) except lock.Locked as e: if application_lock is not None: application_lock.delete() if journal_lock is not None: journal_lock.delete() raise except exceptions.AuthoriseException as e: msg = "Account {x} is not permitted to edit the current update request on journal {y}".format( x=account.id, y=journal.id) app.logger.info(msg) e.message = msg raise app.logger.info( "Using existing application {y} as update request for journal {x}" .format(y=application.id, x=journal.id)) if app.logger.isEnabledFor("debug"): app.logger.debug( "Completed update_request_for_journal; return application object" ) return application, journal_lock, application_lock
def test_01_update_request(self, name, journal_id, journal_lock, account, account_role, account_is_owner, current_applications, application_lock, application_status, completed_applications, raises, return_app, return_jlock, return_alock, db_jlock, db_alock, db_app): ############################################### ## set up # create the journal journal = None jid = None if journal_id == "valid": journal = Journal(**JournalFixtureFactory.make_journal_source( in_doaj=True)) journal.remove_related_applications() journal.remove_current_application() jid = journal.id elif journal_id == "not_in_doaj": journal = Journal(**JournalFixtureFactory.make_journal_source( in_doaj=False)) journal.remove_related_applications() journal.remove_current_application() jid = journal.id elif journal_id == "missing": jid = uuid.uuid4().hex acc = None if account == "yes": acc = Account(**AccountFixtureFactory.make_publisher_source()) if account_role == "none": acc.remove_role("publisher") elif account_role == "admin": acc.remove_role("publisher") acc.add_role("admin") acc.set_id(acc.makeid()) if account_is_owner == "yes": acc.set_id(journal.owner) if journal_lock == "yes": lock.lock("journal", jid, "someoneelse", blocking=True) latest_app = None current_app_count = int(current_applications) for i in range(current_app_count): app = Suggestion( **ApplicationFixtureFactory.make_application_source()) app.set_id(app.makeid()) app.set_created("198" + str(i) + "-01-01T00:00:00Z") app.set_current_journal(jid) app.save() latest_app = app if journal is not None: journal.set_current_application(app.id) comp_app_count = int(completed_applications) for i in range(comp_app_count): app = Suggestion( **ApplicationFixtureFactory.make_application_source()) app.set_id(app.makeid()) app.set_created("197" + str(i) + "-01-01T00:00:00Z") app.set_related_journal(jid) app.save() if journal is not None: journal.add_related_application(app.id, date_accepted=app.created_date) if current_app_count == 0 and comp_app_count == 0: # save at least one record to initialise the index mapping, otherwise tests fail app = Suggestion( **ApplicationFixtureFactory.make_application_source()) app.set_id(app.makeid()) app.save() if application_lock == "yes": lock.lock("suggestion", latest_app.id, "someoneelse", blocking=True) if application_status != "n/a": latest_app.set_application_status(application_status) latest_app.save(blocking=True) # finally save the journal record, ensuring we get a blocking save, so everything # above here should be synchronised with the repo if journal is not None: journal.save(blocking=True) ########################################################### # Execution svc = DOAJ.applicationService() if raises != "": with self.assertRaises(EXCEPTIONS[raises]): svc.update_request_for_journal(jid, acc) else: application, jlock, alock = svc.update_request_for_journal( jid, acc) # we need to sleep, so the index catches up time.sleep(1) if return_app == "none": assert application is None elif return_app == "yes": assert application is not None if return_jlock == "none": assert jlock is None elif return_jlock == "yes": assert jlock is not None if return_alock == "none": assert alock is None elif return_alock == "yes": assert alock is not None if db_jlock == "no" and acc is not None: assert not lock.has_lock("journal", jid, acc.id) elif db_jlock == "yes" and acc is not None: l = lock.has_lock("journal", jid, acc.id) assert lock.has_lock("journal", jid, acc.id) if db_alock == "no" and application.id is not None and acc is not None: assert not lock.has_lock("suggestion", application.id, acc.id) elif db_alock == "yes" and application.id is not None and acc is not None: assert lock.has_lock("suggestion", application.id, acc.id) if db_app == "no" and application.id is not None: indb = Suggestion.q2obj(q="id.exact:" + application.id) assert indb is None elif db_app == "yes" and application.id is not None: indb = Suggestion.q2obj(q="id.exact:" + application.id) assert indb is not None if current_app_count == 0 and comp_app_count == 0 and application is not None: assert application.article_metadata is None assert application.articles_last_year is None elif application is not None: assert application.article_metadata is not None assert application.articles_last_year is not None
def request_handler(request, suggestion_id, redirect_route="admin.suggestion_page", template="admin/suggestion.html", locked_template="admin/suggestion_locked.html", editors=None, group_editable=False, editorial_available=False, status_options="admin"): if not current_user.has_role("edit_suggestion"): abort(401) s = models.Suggestion.pull(suggestion_id) if s is None: abort(404) # attempt to get a lock on the object try: lockinfo = lock.lock("suggestion", suggestion_id, current_user.id) except lock.Locked as l: return render_template(locked_template, suggestion=s, lock=l.lock, edit_suggestion_page=True) current_info = models.ObjectDict(SuggestionFormXWalk.obj2form(s)) form = EditSuggestionForm(request.form, current_info) if status_options == "admin": form.application_status.choices = forms.application_status_choices_admin else: form.application_status.choices = forms.application_status_choices_editor process_the_form = True if request.method == 'POST' and s.application_status == 'accepted': flash('You cannot edit applications which have been accepted into DOAJ.', 'error') process_the_form = False if form.application_status.data == "accepted" and form.make_all_fields_optional.data: flash("You cannot accept an application into the DOAJ without fully validating it", "error") process_the_form = False # add the contents of a few fields to their descriptions since select2 autocomplete # would otherwise obscure the full values if form.publisher.data: if not form.publisher.description: form.publisher.description = 'Full contents: ' + form.publisher.data else: form.publisher.description += '<br><br>Full contents: ' + form.publisher.data if form.society_institution.data: if not form.society_institution.description: form.society_institution.description = 'Full contents: ' + form.society_institution.data else: form.society_institution.description += '<br><br>Full contents: ' + form.society_institution.data if form.platform.data: if not form.platform.description: form.platform.description = 'Full contents: ' + form.platform.data else: form.platform.description += '<br><br>Full contents: ' + form.platform.data if editors is not None: form.editor.choices = [("", "Choose an editor")] + [(editor, editor) for editor in editors] else: if s.editor is not None: form.editor.choices = [(s.editor, s.editor)] else: form.editor.choices = [("", "")] return suggestion_form(form, request, template, existing_suggestion=s, suggestion=s, process_the_form=process_the_form, admin_page=True, subjectstr=subjects2str(s.bibjson().subjects()), lcc_jstree=json.dumps(lcc_jstree), group_editable=group_editable, editorial_available=editorial_available, redirect_route=redirect_route, lock=lockinfo )
def test_01_update_request(self, name, journal_id, journal_lock, account, account_role, account_is_owner, current_applications, application_lock, application_status, completed_applications, raises, return_app, return_jlock, return_alock, db_jlock, db_alock, db_app): ############################################### ## set up # create the journal journal = None jid = None if journal_id == "valid": journal = Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) journal.remove_related_applications() journal.remove_current_application() jid = journal.id elif journal_id == "not_in_doaj": journal = Journal(**JournalFixtureFactory.make_journal_source(in_doaj=False)) journal.remove_related_applications() journal.remove_current_application() jid = journal.id elif journal_id == "missing": jid = uuid.uuid4().hex acc = None if account == "yes": acc = Account(**AccountFixtureFactory.make_publisher_source()) if account_role == "none": acc.remove_role("publisher") elif account_role == "admin": acc.remove_role("publisher") acc.add_role("admin") acc.set_id(acc.makeid()) if account_is_owner == "yes": acc.set_id(journal.owner) if journal_lock == "yes": lock.lock("journal", jid, "someoneelse", blocking=True) latest_app = None current_app_count = int(current_applications) for i in range(current_app_count): app = Suggestion(**ApplicationFixtureFactory.make_application_source()) app.set_id(app.makeid()) app.set_created("198" + str(i) + "-01-01T00:00:00Z") app.set_current_journal(jid) app.save() latest_app = app if journal is not None: journal.set_current_application(app.id) comp_app_count = int(completed_applications) for i in range(comp_app_count): app = Suggestion(**ApplicationFixtureFactory.make_application_source()) app.set_id(app.makeid()) app.set_created("197" + str(i) + "-01-01T00:00:00Z") app.set_related_journal(jid) app.save() if journal is not None: journal.add_related_application(app.id, date_accepted=app.created_date) if current_app_count == 0 and comp_app_count == 0: # save at least one record to initialise the index mapping, otherwise tests fail app = Suggestion(**ApplicationFixtureFactory.make_application_source()) app.set_id(app.makeid()) app.save() if application_lock == "yes": lock.lock("suggestion", latest_app.id, "someoneelse", blocking=True) if application_status != "n/a": latest_app.set_application_status(application_status) latest_app.save(blocking=True) # finally save the journal record, ensuring we get a blocking save, so everything # above here should be synchronised with the repo if journal is not None: journal.save(blocking=True) ########################################################### # Execution svc = DOAJ.applicationService() if raises != "": with self.assertRaises(EXCEPTIONS[raises]): svc.update_request_for_journal(jid, acc) else: application, jlock, alock = svc.update_request_for_journal(jid, acc) # we need to sleep, so the index catches up time.sleep(1) if return_app == "none": assert application is None elif return_app == "yes": assert application is not None if return_jlock == "none": assert jlock is None elif return_jlock == "yes": assert jlock is not None if return_alock == "none": assert alock is None elif return_alock == "yes": assert alock is not None if db_jlock == "no" and acc is not None: assert not lock.has_lock("journal", jid, acc.id) elif db_jlock == "yes" and acc is not None: assert lock.has_lock("journal", jid, acc.id) if db_alock == "no" and application.id is not None and acc is not None: assert not lock.has_lock("suggestion", application.id, acc.id) elif db_alock == "yes" and application.id is not None and acc is not None: assert lock.has_lock("suggestion", application.id, acc.id) if db_app == "no" and application.id is not None: indb = Suggestion.q2obj(q="id.exact:" + application.id) assert indb is None elif db_app == "yes" and application.id is not None: indb = Suggestion.q2obj(q="id.exact:" + application.id) assert indb is not None if current_app_count == 0 and comp_app_count == 0 and application is not None: assert application.article_metadata is None assert application.articles_last_year is None elif application is not None: assert application.article_metadata is not None assert application.articles_last_year is not None