Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editors have more control over publication notifications #4094

Merged
merged 9 commits into from
Jun 4, 2024
31 changes: 27 additions & 4 deletions src/core/email.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from dataclasses import dataclass, field
from typing import Tuple

from django.conf import ImproperlyConfigured
from django.core.files.uploadedfile import InMemoryUploadedFile

from utils import notify_helpers
Expand All @@ -16,9 +17,31 @@ class EmailData:


def send_email(
user, email_data, request, article=None, preprint=None,
log_dict=None,
):
user,
email_data,
request,
article=None,
preprint=None,
log_dict=None,
):
""" A standard way to send email using data from core.forms.EmailForm.

:param user: The main recipient of the email. Can be None if email_data has recipient
:type user: Account or NoneType
:param email_data: The email data, typically generated from EmailForm
:type email_data: EmailData
:param request: The request object
:param article: an article to be used as the log target
:param preprint: a preprint to be used as the log target
:param log_dict: log details to be used instead of a generic email log
"""

if user:
to = user.email
elif email_data.to:
to = email_data.to
else:
raise ImproperlyConfigured('Pass a user or email_data with a to field')
subject = email_data.subject
message = email_data.body

Expand All @@ -34,7 +57,7 @@ def send_email(
notify_helpers.send_email_with_body_from_user(
request,
subject,
user.email,
to,
message,
log_dict=log_dict,
cc=email_data.cc,
Expand Down
2 changes: 2 additions & 0 deletions src/core/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
EditorialGroupForm,
EmailForm,
FileUploadForm,
FullEmailForm,
FullSettingEmailForm,
GeneratedPluginSettingForm,
GeneratedSettingForm,
JournalArticleForm,
Expand Down
24 changes: 23 additions & 1 deletion src/core/forms/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,6 @@ def is_confirmed(self):


class EmailForm(forms.Form):
subject = forms.CharField(max_length=1000)
cc = TagitField(
required=False,
max_length=10000,
Expand All @@ -761,6 +760,7 @@ class EmailForm(forms.Form):
required=False,
max_length=10000,
)
subject = forms.CharField(max_length=1000)
body = forms.CharField(widget=TinyMCE)
attachments = MultipleFileField(required=False)

Expand Down Expand Up @@ -788,6 +788,21 @@ def as_dataclass(self):
return email.EmailData(**self.cleaned_data)


class FullEmailForm(EmailForm):
""" An email form that includes the To field
"""
to = TagitField(
required=True,
max_length=10000,
)

field_order = ['to', 'cc', 'bcc', 'subject', 'body', 'attachments']

def clean_to(self):
to = self.cleaned_data['to']
return self.email_sequence_cleaner("to", to)


class SettingEmailForm(EmailForm):
""" An Email form that populates initial data using Janeway email settings

Expand Down Expand Up @@ -817,6 +832,13 @@ def __init__(self, *args, **kwargs):
setting_name,
)


class FullSettingEmailForm(SettingEmailForm, FullEmailForm):
""" A setting-based email form that includes the To field
"""
pass


class SimpleTinyMCEForm(forms.Form):
""" A one-field form for populating a TinyMCE textarea
"""
Expand Down
34 changes: 9 additions & 25 deletions src/core/migrations/0074_auto_20220721_1448.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from __future__ import unicode_literals

from django.db import migrations
from django.utils import translation
from django.conf import settings as django_settings
from utils import migration_utils


OLD_DEFAULT_VALUE = 'Dear {{ review_assignment.reviewer.full_name }},<br/><br/>We are requesting that you undertake a review of "{{ article.title }}" in {{ article.journal.name }}.<br/><br/>We would be most grateful for your time as the feedback from our reviewers is of the utmost importance to our editorial decision-making processes.<br/><br/>You can let us know your decision or decline to undertake the review: {{ review_url }} <br/><br/>{{ article_details }}<br/><br/>Regards,<br/>{{ request.user.signature|safe }}'
Expand All @@ -15,29 +14,14 @@


def update_default_setting(apps, schema_editor):
"""
Updates the review_assignment setting for a journal where it has not been edited.
"""
with translation.override('en'):
SettingValue = apps.get_model('core', 'SettingValue')
setting_value = SettingValue.objects.filter(
setting__name='review_assignment',
journal=None,
).first()

if setting_value:
language_var = "value_{}".format('en')
setattr(setting_value, language_var, NEW_VALUE)
setting_value.save()

variants_to_delete = SettingValue.objects.filter(
setting__name='review_assignment',
journal__isnull=False,
)

for variant in variants_to_delete:
if getattr(variant, language_var) in [OLD_DEFAULT_VALUE, VARIANT_ONE, VARIANT_TWO]:
variant.delete()

migration_utils.update_default_setting_values(
apps,
setting_name='review_assignment',
group_name='email',
values_to_replace=[OLD_DEFAULT_VALUE, VARIANT_ONE, VARIANT_TWO],
replacement_value=NEW_VALUE,
)


class Migration(migrations.Migration):
Expand Down
13 changes: 12 additions & 1 deletion src/events/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ class Events:
# raised when an Editor notifies an author that publication is set
ON_AUTHOR_PUBLICATION = 'on_author_publication'

# kwargs: request, article, notification_formset
# raised when an editor notifies author, editors,
# and/or reviewers that publication is set
ON_PREPUB_NOTIFICATIONS = 'on_prepub_notifications'

# kwargs: request, override
# raised when an Editor overrides review security
ON_REVIEW_SECURITY_OVERRIDE = 'on_review_security_override'
Expand Down Expand Up @@ -275,6 +280,10 @@ class Events:
# raised when a user access request is evaluated by staff.
ON_ACCESS_REQUEST_COMPLETE = 'on_access_request_complete'

DEPRECATED_EVENTS = {
ON_AUTHOR_PUBLICATION,
}

@staticmethod
def raise_event(event_name, task_object=None, **kwargs):
"""
Expand All @@ -285,6 +294,9 @@ def raise_event(event_name, task_object=None, **kwargs):
:param kwargs: the arguments to pass to the event
:return: None
"""
if event_name in Events.DEPRECATED_EVENTS and settings.DEBUG:
raise DeprecationWarning(f'{ event_name } is deprecated.')

if settings.DEBUG:
print('Firing event {}'.format(event_name))
# destroy/complete tasks that have registered for this event
Expand Down Expand Up @@ -315,4 +327,3 @@ def register_for_event(event_name, *functions):
Events._hooks[event_name] = []

Events._hooks[event_name] += functions

5 changes: 5 additions & 0 deletions src/events/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,12 @@
event_logic.Events.register_for_event(event_logic.Events.ON_CORRECTIONS_CANCELLED,
transactional_emails.send_cancel_corrections)

# Prepublication
event_logic.Events.register_for_event(event_logic.Events.ON_PREPUB_NOTIFICATIONS,
transactional_emails.send_prepub_notifications)

# Publication
# Note: ON_AUTHOR_PUBLICATION is deprecated
event_logic.Events.register_for_event(event_logic.Events.ON_AUTHOR_PUBLICATION,
transactional_emails.send_author_publication_notification)

Expand Down
4 changes: 2 additions & 2 deletions src/journal/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ def _section(self, obj):

class FixedPubCheckItemsAdmin(admin_utils.ArticleFKModelAdmin):
list_display = ('_article', '_journal', 'metadata', 'verify_doi',
'select_issue', 'set_pub_date', 'notify_the_author',
'select_issue', 'set_pub_date', 'send_notifications',
'select_render_galley', 'select_article_image',
'select_open_reviews')
list_filter = ('article__journal', 'metadata', 'verify_doi',
'select_issue', 'set_pub_date', 'notify_the_author',
'select_issue', 'set_pub_date', 'send_notifications',
'select_render_galley', 'select_article_image',
'select_open_reviews')
search_fields = ('article__pk', 'article__title', 'article__journal__code')
Expand Down
20 changes: 20 additions & 0 deletions src/journal/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from tinymce.widgets import TinyMCE

from core import models as core_models
from core.forms import FullSettingEmailForm
from journal import models as journal_models, logic
from utils.forms import CaptchaForm

Expand Down Expand Up @@ -136,3 +137,22 @@ class Meta:
'display_issue_doi',
'display_issues_grouped_by_decade',
)


class BasePrepubNotificationFormSet(forms.BaseFormSet):

def get_form_kwargs(self, index):
kwargs = super().get_form_kwargs(index)
if index == 0:
kwargs['setting_name'] = 'author_publication'
elif index == 1:
kwargs['setting_name'] = 'peer_reviewer_pub_notification'
return kwargs


PrepubNotificationFormSet = forms.formset_factory(
FullSettingEmailForm,
formset=BasePrepubNotificationFormSet,
extra=0,
max_num=2,
)
52 changes: 49 additions & 3 deletions src/journal/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,15 +248,61 @@ def handle_unassign_issue(request, article, issues):
messages.add_message(request, messages.WARNING, 'Issue does not exist.')


def get_notify_author_text(request, article):
context = {
def get_initial_for_prepub_notifications(request, article):
author_initial = {}
author_initial['to'] = article.correspondence_author.email
cc = [au.email for au in article.non_correspondence_authors()]
notify_section_editors = request.journal.get_setting(
'general',
'notify_section_editors_of_publication',
)
if notify_section_editors:
cc.extend([ed.email for ed in article.section_editors()])
author_initial['cc'] = ','.join(cc)

notify_peer_reviewers = request.journal.get_setting(
'general',
'notify_peer_reviewers_of_publication',
)

if not notify_peer_reviewers:
return [author_initial]
else:
peer_reviewer_initial = {}
custom_reply_to = request.journal.get_setting(
'general',
'replyto_address'
)
peer_reviewer_initial['to'] = custom_reply_to or request.user.email
reviewer_emails = article.peer_reviewers(emails=True, completed=True)
peer_reviewer_initial['bcc'] = ','.join(reviewer_emails)
return [author_initial, peer_reviewer_initial]


def handle_prepub_notifications(request, article, formset):
kwargs = {
'request': request,
'article': article,
'formset': formset,
}

return render_template.get_message_content(request, context, 'author_publication')
event_logic.Events.raise_event(
event_logic.Events.ON_PREPUB_NOTIFICATIONS,
task_object=article,
**kwargs,
)
article.fixedpubcheckitems.send_notifications = True
article.fixedpubcheckitems.save()
messages.add_message(
request,
messages.SUCCESS,
'Notifications sent.'
)


def notify_author(request, article):
""" Note: This function is deprecated. Use handle_prepub_notifications instead.
"""
kwargs = {
'request': request,
'article': article,
Expand Down
39 changes: 39 additions & 0 deletions src/journal/migrations/0065_prepub_send_notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Generated by Django 3.2.20 on 2024-04-17 15:26

from django.db import migrations
from utils import migration_utils


def replace_peer_reviewer_pub_notification(apps, schema_editor):
values_to_replace = [
'<p>Dear {{reviewer.full_name}}</p><p>An article that you served as peer reviewer for, \"{{ article.safe_title }}\", has been set for publication.<br><br>The article will be published on {{ article.date_published }}.<br><br>Regards,',
]

replacement_value = '<p>Dear reviewers,</p><p>An article that you served as peer reviewer for, \"{{ article.safe_title }}\", has been set for publication.<br><br>The article will be published on {{ article.date_published }}.<br><br>Regards,'

migration_utils.update_default_setting_values(
apps,
setting_name='peer_reviewer_pub_notification',
group_name='email',
values_to_replace=values_to_replace,
replacement_value=replacement_value,
)


class Migration(migrations.Migration):

dependencies = [
('journal', '0064_journal_default_editorial_team_image'),
]

operations = [
migrations.RenameField(
model_name='fixedpubcheckitems',
old_name='notify_the_author',
new_name='send_notifications',
),
migrations.RunPython(
replace_peer_reviewer_pub_notification,
reverse_code=migrations.RunPython.noop,
),
]
2 changes: 1 addition & 1 deletion src/journal/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1210,7 +1210,7 @@ class FixedPubCheckItems(models.Model):
verify_doi = models.BooleanField(default=False)
select_issue = models.BooleanField(default=False)
set_pub_date = models.BooleanField(default=False)
notify_the_author = models.BooleanField(default=False)
send_notifications = models.BooleanField(default=False)
select_render_galley = models.BooleanField(default=False)
select_article_image = models.BooleanField(default=False)
select_open_reviews = models.BooleanField(default=False)
Expand Down
Loading