diff --git a/cms/data/notifications.yml b/cms/data/notifications.yml
index e918780d80..99a9561210 100644
--- a/cms/data/notifications.yml
+++ b/cms/data/notifications.yml
@@ -128,12 +128,30 @@ bg:job:admin_reports:export_available:
short:
Report "{name}" ready for download
+bg:job:approaching_flag_deadline:notify:
+ long: |
+ "The deadline for the flag in "{journal_title}" (id: {id}) is in 7 days."
+ short:
+ Deadline reminder
+
+flag:assigned:notify:
+ long: |
+ "A new flag in "{journal_title}" (id: {id}) has been assigned to you"
+ short:
+ New flag
+
journal:assed:assigned:notify:
long: |
The journal **{journal_name}** has been assigned to you by the Editor of your group **{group_name}**. Please start work on this within 10 days.
short:
New journal ({issns}) assigned to you
+journal:maned:discontinuing_soon:notify:
+ long: |
+ Journal "{title}" (id: {id}) will discontinue in {days} days.
+ short:
+ Journal discontinuing
+
journal:editor_group:assigned:notify:
long: |
The journal **{journal_name}** has been assigned to your group by a Managing Editor. Please assign this to an Associate Editor within 5 working days.
@@ -186,9 +204,3 @@ update_request:publisher:submitted:notify:
**This is an automated message.**
short:
Your update request ({issns}) has been submitted
-
-journal:maned:discontinuing_soon:notify:
- long: |
- Journal "{title}" (id: {id}) will discontinue in {days} days.
- short:
- Journal discontinuing
diff --git a/dev.template.cfg b/dev.template.cfg
index dd70ea82bb..32381ad6a8 100644
--- a/dev.template.cfg
+++ b/dev.template.cfg
@@ -87,7 +87,8 @@ HUEY_SCHEDULE = {
"datalog_journal_added_update": CRON_NEVER,
"auto_assign_editor_group_data": CRON_NEVER,
"ris_export": CRON_NEVER,
- "site_statistics": CRON_NEVER
+ "site_statistics": CRON_NEVER,
+ "approaching_flag_deadline": CRON_NEVER
}
###########################################
diff --git a/doajtest/testbook/flagged_journals/flagged_journals.yml b/doajtest/testbook/flagged_journals/flagged_journals.yml
index ce135de75f..5bbb99a57c 100644
--- a/doajtest/testbook/flagged_journals/flagged_journals.yml
+++ b/doajtest/testbook/flagged_journals/flagged_journals.yml
@@ -8,24 +8,32 @@ tests:
role: Admin
steps:
- step: Navigate to /testdrive/flags
- - step: Login as an admin
- - step: Open Feline Aerodynamics Review application (available on your dashboard)
+ - step: In a separate tab login as LordWiggleworth (admin)
+ - step: Open Feline Aerodynamics Review journal (available on your dashboard)
results:
- Above the notes there is the Add Flag button visible and active
- step: Click Add Flag button
results:
- An empty flag form is displayed
- - step: In assigned_to input attempt to add editor's id (you can find it in your testdrive/flags data)
+ - step: In assigned_to input attempt to add "MadamPonderleaf" (an editor)
results:
- No matches found is displayed and it is not possible to assign the editor
- - step: In assigned_to input add random_user's id (you can find it in your testdrive/flags data)
+ - step: In assigned_to input add "ProfessorQuibbleton" (admin)
results:
- Id is found and it can be selected
- - step: Select the random_user's id
+ - step: Select "ProfessorQuibbleton"
- step: In deadline input add an improbable date (e.g., 2025-02-31)
results:
- It's not possible to enter an improbable date
- - step: In deadline input add a valid date
+ - step: In deadline input add a date in a past
+ results:
+ - A warning "Provided deadline is in the past. Is it correct?" is displayed under the deadline input
+ - step: Save the form (do not close it)
+ results:
+ - Journal form is saved correctly
+ - step: In deadline input add a date in exactly 1 week
+ results:
+ - Warning disappears
- step: In text area add flag's note (any text)
- step: Save application
results:
@@ -72,6 +80,53 @@ tests:
- In the flag form, in the Assign a User input, there is your id with a red flag icon
- Resolve Flag button is active and all fields are editable
+ - title: Admin - Flags Notifications
+ context:
+ role: Admin
+ setup:
+ - If you haven't performed previous test (Admin - Add, Edit, Resolve) prepare the Journal by following all the steps; otherwise you may omit steps 2-9
+ steps:
+ - step: Navigate to /testdrive/flags
+ - step: In a separate tab login as LordWiggleworth (admin)
+ - step: Open Feline Aerodynamics Review journal (available on your dashboard)
+ - step: Click Add Flag button
+ - step: Assign "ProfessorQuibbleton" (admin)
+ - step: Add deadline in exactly 1 week
+ - step: In text area add flag's note (any text)
+ - step: Save application, Unlock & Close the form
+ - step: Log out
+ - step: In a separate tab log in as "ProfessorQuibbleton"
+ results:
+ - At the top navbar Notifications indicate a new notification
+ - step: Hover over Notifications button
+ result:
+ - At the top of the list new notification "New flag" is listed
+ - step: Click on the notification
+ results:
+ - Feline Aerodynamics Review journal form is opened in a new tab
+ - On the right the flag is displayed and assigned to you ("ProfessorQuibbleton")
+ - step: Unlock & Close the form, close the tab
+ - step: Click the Notifications button at the top navigation bar
+ results:
+ - /dashboard/notifications opens
+ - At the top of the list "New flag" notification is displayed.
+ - The long description says "A new flag in 'Feline Aerodynamics Review' (id) has been assigned to you"
+ - step: Open the testdrive/flags page do not close the page where you are logged in
+ - step: Click "Run background task" button, wait for the task to execute
+ result:
+ - Message "Script executed successfully!" is displayed
+ - step: Go back to the tab where you are logged as ProfessorQuibbleton
+ - step: refresh the page
+ - step: navigate to /dashboard/notifications if not already there
+ results:
+ - At the top of the notifications list a new notification "Deadline reminder" is listed
+ - Long description says "The deadline for the flag in 'Feline Aerodynamics Review' (id) is in 7 days."
+ - At the top navbar Notifications button has a new notification marked
+ - step: Click "See action" link at the right of the new "Deadline reminder" notification
+ results:
+ - Feline Aerodynamics Review journal form is opened in a new tab
+ - step: Unlock & Close the form, close the tab
+
- title: Admin - Dashboard [NOT CURRENTLY USED]
context:
role: Admin
@@ -129,7 +184,7 @@ tests:
role: Editor
steps:
- step: Navigate to /testdrive/flags
- - step: Login as an editor
+ - step: Login as MadamPonderleaf (editor)
- step: Navigate to /applications
- step: Open to Open Journal of Intergalactic Diplomacy - make sure you haven't resolved the flag while logged as an admin in one of the previous tests
results:
diff --git a/doajtest/testdrive/flags.py b/doajtest/testdrive/flags.py
index 37a57fa6ed..bd11f8ac0f 100644
--- a/doajtest/testdrive/flags.py
+++ b/doajtest/testdrive/flags.py
@@ -1,4 +1,4 @@
-from doajtest.fixtures import ApplicationFixtureFactory
+from doajtest.fixtures import JournalFixtureFactory
from doajtest.testdrive.factory import TestDrive
from portality.lib import dates
from portality import models, constants
@@ -8,9 +8,10 @@ class Flags(TestDrive):
def __init__(self):
self.another_eg = None
- self.apps = []
+ self.journals = []
self.admin_password = None
self.admin = None
+ self.anotheradmin = None
self.editor = None
self.editor_password = None
self.random_user = None
@@ -18,14 +19,19 @@ def __init__(self):
self.eg = None
def setup(self) -> dict:
- self.create_accounts()
- self.build_applications()
+ random_str = self.create_random_str()
+ self.create_accounts(random_str)
+ self.build_journals(random_str)
return {
"accounts": {
"admin": {
"username": self.admin.id,
"password": self.admin_password
},
+ "another admin": {
+ "username": self.anotheradmin.id,
+ "password": self.anotheradmin_password
+ },
"editor": {
"username": self.editor.id,
"password": self.editor_password
@@ -35,30 +41,42 @@ def setup(self) -> dict:
"password": self.random_user_password
}
},
- "applications": self.apps,
+ "journals": self.journals,
"non_renderable": {
"editor_groups": [self.eg.name, self.another_eg.name]
+ },
+ "script": {
+ "script_name": "approaching_flag_deadline",
+ "title": "Run background task"
}
}
- def create_accounts(self):
- admin_name = self.create_random_str()
+ def create_accounts(self, random_str):
+
+ admin_name = "LordWiggleworth_" + random_str
self.admin_password = self.create_random_str()
self.admin = models.Account.make_account(admin_name + "@example.com", admin_name, "FlagsManed " + admin_name,
["admin", "editor"])
self.admin.set_password(self.admin_password)
self.admin.save()
- random_name = self.create_random_str()
+ anotheradmin_name = "ProfessorQuibbleton_" + random_str
+ self.anotheradmin_password = self.create_random_str()
+ self.anotheradmin = models.Account.make_account(anotheradmin_name + "@example.com", anotheradmin_name, "Admin " + anotheradmin_name,
+ ["admin", "editor"])
+ self.anotheradmin.set_password(self.anotheradmin_password)
+ self.anotheradmin.save()
+
+ random_name = "BaronFeatherfall_" + random_str
self.random_user_password = self.create_random_str()
self.random_user = models.Account.make_account(random_name + "@example.com", random_name,
- "FlagsManed " + random_name,
+ "Admin " + random_name,
["admin"])
self.random_user.set_password(self.random_user_password)
self.random_user.save()
- editor_name = self.create_random_str()
- self.editor = models.Account.make_account(editor_name + "@example.com", editor_name, "editor " + editor_name,
+ editor_name = "MadamPonderleaf_" + random_str
+ self.editor = models.Account.make_account(editor_name + "@example.com", editor_name, "Editor " + editor_name,
["editor"])
self.editor_password = self.create_random_str()
self.editor.set_password(self.editor_password)
@@ -80,11 +98,11 @@ def create_accounts(self):
self.another_eg.set_editor(self.editor.id)
self.another_eg.save()
- def build_applications(self):
- applications = [
+ def build_journals(self, random_str):
+ journals = [
{
"type": models.Journal,
- "title": "Journal of Quantum Homeopathy",
+ "title": "Journal of Quantum Homeopathy " + random_str,
"assigned_to": self.admin.id,
"flagged_to": self.admin.id,
"group": self.eg.name,
@@ -92,10 +110,8 @@ def build_applications(self):
"note": "Peer review process unclear. The journal claims to use “ancient wisdom and telepathic consensus” to select papers. Should we request further clarification, or just accept that the universe decides?"
},
{
- "type": models.Application,
- "title": "The Mars Agricultural Review",
- "application_type": constants.APPLICATION_TYPE_NEW_APPLICATION,
- "status": "in progress",
+ "type": models.Journal,
+ "title": "The Mars Agricultural Review " + random_str,
"assigned_to": self.editor.id,
"flagged_to": self.admin.id,
"group": self.eg.name,
@@ -103,19 +119,16 @@ def build_applications(self):
"note": "Ethical concerns? Their conflict of interest statement is just 'Trust us.' Also, every editorial board member shares the same last name. Suspicious? Or just an enthusiastic family business?"
},
{
- "type": models.Application,
- "title": "Cryptid Behavioral Studies Quarterly",
- "application_type": constants.APPLICATION_TYPE_UPDATE_REQUEST,
+ "type": models.Journal,
+ "title": "Cryptid Behavioral Studies Quarterly " + random_str,
"assigned_to": self.editor.id,
"flagged_to": self.admin.id,
"group": self.eg.name,
"note": "Formatting issues. Their abstracts are in Comic Sans, their references are in Wingdings, and their figures appear to be hand-drawn with crayon. Surprisingly, it almost adds to the charm."
},
{
- "type": models.Application,
- "title": "The Bermuda Triangle Journal of Lost and Found",
- "application_type": constants.APPLICATION_TYPE_NEW_APPLICATION,
- "status": "on hold",
+ "type": models.Journal,
+ "title": "The Bermuda Triangle Journal of Lost and Found " + random_str,
"assigned_to": self.editor.id,
"flagged_to": self.editor.id,
"group": self.eg.name,
@@ -123,14 +136,13 @@ def build_applications(self):
},
{
"type": models.Journal,
- "title": "Feline Aerodynamics Review",
+ "title": "Feline Aerodynamics Review " + random_str,
"assigned_to": self.admin.id,
"group": self.eg.name
},
{
- "type": models.Application,
- "title": "Journal of Intergalactic Diplomacy",
- "application_type": constants.APPLICATION_TYPE_NEW_APPLICATION,
+ "type": models.Journal,
+ "title": "Journal of Intergalactic Diplomacy " + random_str,
"assigned_to": self.random_user.id,
"flagged_to": self.admin.id,
"group": self.another_eg.name,
@@ -138,9 +150,8 @@ def build_applications(self):
"note": "Editorial process... innovative? They claim to have a 100% acceptance rate because “rejecting knowledge is against our values.” Admirable, but I feel like that’s not how this works."
},
{
- "type": models.Application,
- "title": "Applied Alchemy & Unstable Chemistry",
- "application_type": constants.APPLICATION_TYPE_UPDATE_REQUEST,
+ "type": models.Journal,
+ "title": "Applied Alchemy & Unstable Chemistry " + random_str,
"assigned_to": self.random_user.id,
"flagged_to": self.editor.id,
"note": "Journal scope mismatch. The journal is called The International Review of Advanced Neuroscience but 90\% of its articles are about cat memes. Honestly, I’d subscribe, but should we approve it?",
@@ -148,22 +159,16 @@ def build_applications(self):
}
]
- for record in applications:
- source = ApplicationFixtureFactory.make_application_source()
- ap = models.Application(**source)
- if "application_type" in record:
- source["admin"]["application_type"] = record["application_type"]
+ for record in journals:
+ source = JournalFixtureFactory.make_journal_source(True)
+ ap = models.Journal(**source)
bj = ap.bibjson()
bj.title = record["title"]
ap.set_id(ap.makeid())
ap.set_last_manual_update(dates.today())
ap.set_created(dates.before_now(200))
- ap.remove_current_journal()
- ap.remove_related_journal()
ap.set_editor_group(record["group"])
ap.set_editor(record["assigned_to"])
- if "status" in record:
- ap.set_application_status(record["status"])
if "flagged_to" in record:
note = {"id": self.create_random_str(),
"note": record["note"],
@@ -176,14 +181,16 @@ def build_applications(self):
}}
ap.set_notes(note)
ap.save()
- self.apps.append(ap.id)
+ self.journals.append(ap.id)
+
+ return self.journals
def teardown(self, params):
for acc in params.get("accounts").values():
models.Account.remove_by_id(acc["username"])
- for app in params.get("applications"):
- models.Application.remove_by_id(app)
+ for app in params.get("journals"):
+ models.Journal.remove_by_id(app)
print(params.get("non_renderable"))
print(params.get("non_renderable").get("editor_groups"))
diff --git a/doajtest/unit/event_consumers/test_flag_assigned.py b/doajtest/unit/event_consumers/test_flag_assigned.py
new file mode 100644
index 0000000000..cdff3236b6
--- /dev/null
+++ b/doajtest/unit/event_consumers/test_flag_assigned.py
@@ -0,0 +1,53 @@
+from portality import models
+from portality import constants
+from portality.bll import exceptions
+from doajtest.helpers import DoajTestCase
+from portality.events.consumers.flag_assigned import FlagAssigned
+from doajtest.fixtures import JournalFixtureFactory
+import time
+
+
+class TestFlagAssigned(DoajTestCase):
+ def setUp(self):
+ super(TestFlagAssigned, self).setUp()
+
+ def tearDown(self):
+ super(TestFlagAssigned, self).tearDown()
+
+ def test_should_consume(self):
+ event = models.Event(constants.EVENT_FLAG_ASSIGNED, context={"assignee": "rudolph", "journal": JournalFixtureFactory.make_journal_source()})
+ assert FlagAssigned.should_consume(event)
+
+ def test_consume_success(self):
+ with self._make_and_push_test_context_manager("/"):
+
+ jsource = JournalFixtureFactory.make_journal_source()
+ j = models.Journal(**jsource)
+ j.save()
+
+ acc = models.Account()
+ acc.set_id("LadyBranbury")
+ acc.set_email("ladybranbury@example.com")
+ acc.save()
+
+ event = models.Event(constants.EVENT_FLAG_ASSIGNED, context={"journal": j.data, "assignee": acc.id})
+ FlagAssigned.consume(event)
+
+ time.sleep(1)
+ ns = models.Notification.all()
+ assert len(ns) == 1
+
+ n = ns[0]
+ assert n.who == "LadyBranbury"
+ assert n.created_by == FlagAssigned.ID
+ assert n.classification == constants.NOTIFICATION_CLASSIFICATION_ASSIGN
+ assert n.long is not None
+ assert n.short is not None
+ assert n.action is not None
+ assert not n.is_seen()
+
+ def test_consume_fail(self):
+ event = models.Event(constants.EVENT_FLAG_ASSIGNED, context={"application": {'stuff': 'nonsense'}})
+ with self.assertRaises(exceptions.NoSuchObjectException):
+ FlagAssigned.consume(event)
+
diff --git a/doajtest/unit/test_find_approaching_deadlines.py b/doajtest/unit/test_find_approaching_deadlines.py
new file mode 100644
index 0000000000..19469f2c19
--- /dev/null
+++ b/doajtest/unit/test_find_approaching_deadlines.py
@@ -0,0 +1,52 @@
+import time
+
+from portality.constants import BgjobOutcomeStatus
+from portality.lib import dates
+from portality.core import app
+from portality.tasks import approaching_flag_deadline
+from portality.background import BackgroundApi
+from doajtest.fixtures import JournalFixtureFactory
+from portality.models import Journal
+from doajtest.helpers import DoajTestCase
+from portality.ui.messages import Messages
+
+
+class TestFindApproachingDeadlines(DoajTestCase):
+
+ def setUp(self):
+ self.journal_found_message = Messages.JOURNALS_WITH_APPROACHING_DEADLINES_FOUND[:15]
+ super(TestFindApproachingDeadlines, self).setUp()
+
+ def tearDown(self):
+ super(TestFindApproachingDeadlines, self).tearDown()
+
+ def test_1_success(self):
+
+ jsource = JournalFixtureFactory.make_journal_source(in_doaj=True)
+ imaginary_agriculture = Journal(**jsource)
+ imaginary_agriculture.set_id("imaginary_agriculture")
+ imaginary_agriculture.bibjson().title = "International Journal of Imaginary Agriculture"
+ imaginary_agriculture.add_note("This is not very urgent", date=dates.today(), author_id="ProfessorSnifflepuff", assigned_to="DameQuacksalot", deadline=dates.format(dates.days_after_now(30), dates.FMT_DATE_STD))
+ imaginary_agriculture.save(blocking=True)
+
+ jsource2 = JournalFixtureFactory.make_journal_source(in_doaj=True)
+ unlikely_engineering = Journal(**jsource2)
+ unlikely_engineering.set_id("annalsofunlikelyengineering")
+ unlikely_engineering.bibjson().title = "Annals of Unlikely Engineering"
+ unlikely_engineering.add_note("This deadline is soon", date=dates.today(), author_id="ProfessorSnifflepuff",
+ assigned_to="DameQuacksalot", deadline=dates.format(dates.days_after_now(app.config.get('FLAG_APPROACHING_DEADLINE_DELTA', 7)), dates.FMT_DATE_STD))
+ unlikely_engineering.save(blocking=True)
+
+
+ user = app.config.get("SYSTEM_USERNAME")
+ job = approaching_flag_deadline.ApproachingFlagDeadlineTask.prepare(user)
+ task = approaching_flag_deadline.ApproachingFlagDeadlineTask(job)
+ BackgroundApi.execute(task)
+
+ job = task.background_job
+ assert job.status == "complete"
+ assert len(job.audit) > 2
+ journal_found_log = [p["message"] for p in job.audit if p["message"].startswith(self.journal_found_message)]
+ assert len(journal_found_log) == 1
+ assert journal_found_log[0].split(": ")[-1] == unlikely_engineering.id
+ assert job.outcome_status == BgjobOutcomeStatus.Success
\ No newline at end of file
diff --git a/portality/bll/services/events.py b/portality/bll/services/events.py
index 27f3fbe7d6..0a214b6610 100644
--- a/portality/bll/services/events.py
+++ b/portality/bll/services/events.py
@@ -27,6 +27,7 @@
from portality.events.consumers.application_editor_acceptreject_notify import ApplicationEditorAcceptRejectNotify
from portality.events.consumers.update_request_maned_editor_group_assigned_notify import UpdateRequestManedEditorGroupAssignedNotify
from portality.events.consumers.article_ris_generator import ArticleRISGenerator
+from portality.events.consumers.flag_assigned import FlagAssigned
@@ -55,6 +56,7 @@ class EventsService(object):
ApplicationPublisherCreatedNotify,
ApplicationPublisherQuickRejectNotify,
ArticleRISGenerator,
+ FlagAssigned,
BGJobFinishedNotify,
JournalDiscontinuingSoonNotify,
UpdateRequestManedEditorGroupAssignedNotify,
diff --git a/portality/constants.py b/portality/constants.py
index a0cd15f521..b20a1d2646 100644
--- a/portality/constants.py
+++ b/portality/constants.py
@@ -85,6 +85,7 @@
EVENT_JOURNAL_EDITOR_GROUP_ASSIGNED = "journal:editor_group:assigned"
EVENT_JOURNAL_DISCONTINUING_SOON = "journal:discontinuing_soon"
EVENT_ARTICLE_SAVE = "article:save"
+EVENT_FLAG_ASSIGNED = "flag:assigned"
NOTIFICATION_CLASSIFICATION_STATUS = "alert"
NOTIFICATION_CLASSIFICATION_STATUS_CHANGE = "status_change"
diff --git a/portality/crosswalks/journal_form.py b/portality/crosswalks/journal_form.py
index 0b3bed7404..d3556b48d8 100644
--- a/portality/crosswalks/journal_form.py
+++ b/portality/crosswalks/journal_form.py
@@ -32,6 +32,18 @@ def is_new_editor(cls, form, old):
new_ed = form.editor.data
return old_ed != new_ed and new_ed is not None and new_ed != ""
+ @classmethod
+ def is_new_flag_assignee(cls, form, old):
+ # This method assumes only one flag per journal
+ old_flags = old.flags
+ old_flag_assignee = old_flags[0]["flag"]["assigned_to"] if old_flags else None
+ new_flags = form.flags.data
+ new_flag_assignee = None
+ is_resolved = form.flags.data[0]["flag_resolved"] or form.flags.data[0]["flag_resolved"] == "false"
+ if not is_resolved and new_flags:
+ new_flag_assignee = form.flags.data[0]["flag_assignee"]
+ return old_flag_assignee != new_flag_assignee if new_flag_assignee else False
+
@classmethod
def form_diff(cls, a_formdata, b_formdata):
diff --git a/portality/events/consumers/flag_assigned.py b/portality/events/consumers/flag_assigned.py
new file mode 100644
index 0000000000..55784786f8
--- /dev/null
+++ b/portality/events/consumers/flag_assigned.py
@@ -0,0 +1,51 @@
+# ~~ FlagAssigned:Consumer ~~
+from portality.ui.messages import Messages
+from portality.util import url_for
+from portality.events.consumer import EventConsumer
+from portality import constants
+from portality.bll import exceptions
+from portality import models
+from portality.bll import DOAJ
+
+class FlagAssigned(EventConsumer):
+ ID = "flag:assigned:notify"
+
+ @classmethod
+ def should_consume(cls, event):
+ return event.id == constants.EVENT_FLAG_ASSIGNED and event.context.get("assignee") is not None
+
+ @classmethod
+ def consume(cls, event):
+ assignee_id = event.context.get("assignee")
+ j_source = event.context.get("journal")
+
+ try:
+ journal = models.Journal(**j_source)
+ except Exception as e:
+ raise exceptions.NoSuchObjectException(
+ Messages.EXCEPTION_UNABLE_TO_CONSTRUCT_JOURNAL.format(x=e))
+
+ acc = models.Account.pull(assignee_id)
+
+ if not acc:
+ raise exceptions.NoSuchObjectException(Messages.EXCEPTION_NOTIFICATION_NO_ACCOUNT.format(x=assignee_id))
+
+ if not acc.email:
+ raise exceptions.NoSuchPropertyException(Messages.EXCEPTION_NOTIFICATION_NO_EMAIL.format(x=acc.id))
+
+ # ~~-> Notifications:Service ~~
+ svc = DOAJ.notificationsService()
+
+ notification = models.Notification()
+ notification.classification = constants.NOTIFICATION_CLASSIFICATION_ASSIGN
+ notification.who = acc.id
+ notification.created_by = cls.ID
+ notification.long = svc.long_notification(cls.ID).format(
+ journal_title=journal.bibjson().title,
+ id=journal.id)
+
+ notification.short = svc.short_notification(cls.ID)
+ notification.action = url_for("admin.journal_page", journal_id=journal.id)
+
+ svc.notify(notification)
+ return notification
diff --git a/portality/forms/application_processors.py b/portality/forms/application_processors.py
index 8e033d80bc..a2b8d7ff9c 100644
--- a/portality/forms/application_processors.py
+++ b/portality/forms/application_processors.py
@@ -882,6 +882,8 @@ def finalise(self, account=None):
changed_by=changed_by)
self.target.add_note(n, date=dates.now_str(), author_id=changed_by)
+ is_new_flag_assignee = JournalFormXWalk.is_new_flag_assignee(self.form, self.source)
+
# Save the target
self.target.set_last_manual_update()
self.target.save()
@@ -911,6 +913,13 @@ def finalise(self, account=None):
# self.add_alert("Problem sending email to associate editor - probably address is invalid")
# app.logger.exception('Error sending assignment email to associate.')
+ if is_new_flag_assignee:
+ eventsSvc = DOAJ.eventsService()
+ eventsSvc.trigger(models.Event(constants.EVENT_FLAG_ASSIGNED, current_user.id, {
+ "assignee": self.target.flags[0]["flag"]["assigned_to"],
+ "journal": self.target.data
+ }))
+
def validate(self):
# make use of the ability to disable validation, otherwise, let it run
if self.form is not None:
diff --git a/portality/scripts/approaching_flag_deadline.py b/portality/scripts/approaching_flag_deadline.py
new file mode 100644
index 0000000000..9222b80ed2
--- /dev/null
+++ b/portality/scripts/approaching_flag_deadline.py
@@ -0,0 +1,16 @@
+from portality.core import app
+from portality.tasks import approaching_flag_deadline
+from portality.background import BackgroundApi
+
+def main():
+ if app.config.get("SCRIPTS_READ_ONLY_MODE", False):
+ print("System is in READ-ONLY mode, script cannot run")
+ exit()
+
+ user = app.config.get("SYSTEM_USERNAME")
+ job = approaching_flag_deadline.ApproachingFlagDeadlineTask.prepare(user)
+ task = approaching_flag_deadline.ApproachingFlagDeadlineTask(job)
+ BackgroundApi.execute(task)
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/portality/settings.py b/portality/settings.py
index 0b0c4fef8c..077ada9f11 100644
--- a/portality/settings.py
+++ b/portality/settings.py
@@ -32,6 +32,9 @@
# CAUTION - this can modify the index so should NEVER be used in production!
TESTDRIVE_ENABLED = False
+# List of script names which can be executed via the testdrive.
+TESTDRIVE_SCRIPT_WHITELIST = ["approaching_flag_deadline"]
+
####################################
# Debug Mode
@@ -451,6 +454,7 @@
"auto_assign_editor_group_data": {"month": "*", "day": "*/7", "day_of_week": "*", "hour": "3", "minute": "30"},
"ris_export": {"month": "*", "day": "15", "day_of_week": "*", "hour": "3", "minute": "30"},
"site_statistics": {"month": "*", "day": "*", "day_of_week": "*", "hour": "*", "minute": "40"},
+ "approaching_flag_deadline": {"month": "*", "day": "*", "day_of_week": "*", "hour": "0", "minute": "45"},
}
@@ -1582,6 +1586,9 @@
'find_discontinued_soon': {
'last_run_successful_in': _DAY + _HOUR
},
+ 'approaching_flag_deadline': {
+ 'last_run_successful_in': _DAY + _HOUR
+ },
'harvest': {
'last_run_successful_in': _DAY + _HOUR
},
@@ -1628,6 +1635,12 @@
# report journals that discontinue in ... days (eg. 1 = tomorrow)
DISCONTINUED_DATE_DELTA = 0
+####################################################
+# Flag management
+
+# find approaching deadlines in ... days (eg. 1 = tomorrow)
+FLAG_APPROACHING_DEADLINE_DELTA = 7
+
##################################################
# Feature tours currently active
@@ -1758,4 +1771,4 @@
##################################################
# Object validation settings
-SEAMLESS_JOURNAL_LIKE_SILENT_PRUNE = False
\ No newline at end of file
+SEAMLESS_JOURNAL_LIKE_SILENT_PRUNE = False
diff --git a/portality/static/js/edges/admin.background_jobs.edge.js b/portality/static/js/edges/admin.background_jobs.edge.js
index ed94d43aa6..ceead92ffc 100644
--- a/portality/static/js/edges/admin.background_jobs.edge.js
+++ b/portality/static/js/edges/admin.background_jobs.edge.js
@@ -44,10 +44,10 @@ $.extend(true, doaj, {
// add the date added to doaj
if (resultobj.created_date) {
- dateRow += "Job Created: " + doaj.dates.humanYearMonth(resultobj.created_date) + "
";
+ dateRow += "Job Created: " + resultobj.created_date + "
";
}
if (resultobj.last_updated) {
- dateRow += "Job Last Updated: " + doaj.dates.humanYearMonth(resultobj.last_updated) + "
";
+ dateRow += "Job Last Updated: " + resultobj.last_updated + "
";
}
var paramsBlock = "";
diff --git a/portality/static/js/edges/notifications.edge.js b/portality/static/js/edges/notifications.edge.js
index 0b70fe52f1..6081b5c5c1 100644
--- a/portality/static/js/edges/notifications.edge.js
+++ b/portality/static/js/edges/notifications.edge.js
@@ -10,7 +10,7 @@ $.extend(true, doaj, {
seen_url: "/dashboard/notifications/{notification_id}/seen",
icons: {
- alert: `