Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/md/contact-messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Contact messages

Janeway provides a public contact form for journal and press sites. Contact people can be set for each.

## Data model

Contact messages are stored via `LogEntry` objects with `types="Contact Message"`. The `target`, which would otherwise be something like an article for workflow log entries, is the journal or press site that the contact message was submitted to.

## Recipient access to contact messages

The primary way users get contact messages is directly as emails to their inbox outside of Janeway.

However, they can also access **Contact Messages** via the manager. This view only shows the user messages sent to them, including at the staff level. This may be counter-intuitive in comparison with similar views, because most of the time, an object belonging to journal (like an article, or a task) is viewable by all editors. However, this behavior is needed for privacy, because many users will expect their message to be private to the person they select on the form. At the staff level, staff members cannot see contact messages sent to journal editors, for similar reasons.

## What happens on deletion

When a `ContactPerson` is removed, the contact messages sent to that person should still be viewable, because they are recorded as `LogEntry` objects, where the recipient’s email is saved independently of the `ContactPerson` or `Account`. The message will still appear on **Contact Messages**.

When an `Account` is removed, the `ContactPerson` is also deleted, but again, the contact messages should still exist.
11 changes: 11 additions & 0 deletions docs/md/sequence-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Sequence fields

We use several patterns across Janeway to let users set the sequence of things, most commonly with a `PositiveIntegerField`.

Because we sometimes expose this number to end users in form inputs, it is worth thinking about the usability of the default value. The number 1 is low enough to be easy for end users to manipulate. Zero is sometimes most convenient from a programming perspective, but avoid it if possible, since it can be counter-intuitive for non-programmers.

```py
sequence = models.PositiveIntegerField(default=1)
```

Of course, it is best if end users do not have to deal with this number at all. User interfaces should use accessible buttons that move things up or down in the sequence. This allows us to write an algorithm to check that multiple things have not been given the same sequence, and it keeps the user from having to recall off-screen information about the order of other items whilst performing an action.
26 changes: 26 additions & 0 deletions docs/md/task-and-email-logs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Task and email logs

Janeway creates a number of logs (`utils.LogEntry`) for actions that happen during the workflow. Many these actions trigger an email to be sent. The logging is thus managed by the email sending process. A log is created created that records the type of action taken in Janeway as well as details of the email.

## The notification system

The notification system, where emails and logs are created, lives in `src/utils`, and uses hooks to provide plugin functionality:

```
src/utils/notify.py
src/utils/notify_helpers.py
src/utils/notify_plugins/notify_email.py
src/utils/notify_plugins/email_log.py
src/utils/notify_plugins/notify_slack.py
```

<!--
TODO
* Document how the notify plugins work, exactly
* Document when to use functions like send_email_with_body_from_user, etc.

-->

## Non-workflow log entries

There are email messages stored as log entries that are unrelated to the workflow. Contact messages are the first case of this kind of non-workflow log entry.
10 changes: 10 additions & 0 deletions docs/md/testing-views.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,13 @@ from utils.shared import clear_cache

clear_cache()
```

## Testing views with captchas

Captchas are inserted depending on the `CAPTCHA_TYPE` Django setting, and forms that require them will come up invalid in tests. Disable the captcha during a test run by overriding the setting with an empty string:

```
@override_settings(CAPTCHA_TYPE="")
def test_posting_view_with_captcha(self):
...
```
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ ua-parser==0.16.1
# See #3736
urllib3<2
user-agents==2.2.0
docutils==0.21.2
42 changes: 16 additions & 26 deletions src/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ class AccountAdmin(UserAdmin):
admin_utils.RepositoryRoleInline,
admin_utils.EditorialGroupMemberInline,
admin_utils.StaffGroupMemberInline,
admin_utils.ContactPersonInline,
admin_utils.PasswordResetInline,
]

Expand Down Expand Up @@ -558,36 +559,26 @@ def _journal(self, obj):
return obj.group.journal if obj else ""


class ContactsAdmin(admin.ModelAdmin):
list_display = ("name", "email", "role", "object", "sequence")
class ContactPersonAdmin(admin.ModelAdmin):
list_display = ("_name", "_email", "role", "object", "sequence")
list_filter = (
admin_utils.GenericRelationJournalFilter,
admin_utils.GenericRelationPressFilter,
)
search_fields = ("name", "email", "role")


class ContactAdmin(admin.ModelAdmin):
list_display = (
"subject",
"sender",
"recipient",
"client_ip",
"date_sent",
"object",
)
list_filter = (
admin_utils.GenericRelationJournalFilter,
admin_utils.GenericRelationPressFilter,
"date_sent",
"recipient",
)
search_fields = (
"subject",
"sender",
"recipient",
"account__first_name",
"account__middle_name",
"account__last_name",
"account__email",
"role",
)
date_hierarchy = "date_sent"
raw_id_fields = ("account",)

def _name(self, obj):
return obj.account.full_name() if obj and obj.account else ""

def _email(self, obj):
return obj.account.email if obj and obj.account else ""


class DomainAliasAdmin(admin.ModelAdmin):
Expand Down Expand Up @@ -766,8 +757,7 @@ def _person(self, obj):
(models.Workflow, WorkflowAdmin),
(models.WorkflowLog, WorkflowLogAdmin),
(models.LoginAttempt, LoginAttemptAdmin),
(models.Contacts, ContactsAdmin),
(models.Contact, ContactAdmin),
(models.ContactPerson, ContactPersonAdmin),
(models.AccessRequest, AccessRequestAdmin),
(models.Organization, OrganizationAdmin),
(models.OrganizationName, OrganizationNameAdmin),
Expand Down
3 changes: 2 additions & 1 deletion src/core/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
GetResetTokenForm,
JournalArticleForm,
JournalAttributeForm,
JournalContactForm,
ContactMessageForm,
ContactPersonForm,
JournalImageForm,
JournalStylingForm,
JournalSubmissionForm,
Expand Down
41 changes: 36 additions & 5 deletions src/core/forms/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import uuid
import json
import os
import warnings

from django import forms
from django.db.models import Q
Expand Down Expand Up @@ -33,6 +34,7 @@
YesNoRadio,
)
from utils.logger import get_logger
from utils.models import ACTOR_EMAIL_MAX_LENGTH
from submission import models as submission_models

logger = get_logger(__name__)
Expand Down Expand Up @@ -85,18 +87,16 @@ def clean(self):
return cleaned_data


class JournalContactForm(JanewayTranslationModelForm):
class ContactPersonForm(JanewayTranslationModelForm):
def __init__(self, *args, **kwargs):
next_sequence = kwargs.pop("next_sequence", None)
super(JournalContactForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
if next_sequence:
self.fields["sequence"].initial = next_sequence

class Meta:
model = models.Contacts
model = models.ContactPerson
fields = (
"name",
"email",
"role",
"sequence",
)
Expand All @@ -106,6 +106,37 @@ class Meta:
)


class ContactMessageForm(CaptchaForm):
contact_person = forms.TypedChoiceField(
label=_("Who would you like to contact?"),
)
sender = forms.EmailField(
max_length=ACTOR_EMAIL_MAX_LENGTH,
label=_("Your contact email address"),
)
subject = forms.CharField(max_length=300, label=_("Subject"))
body = JanewayBleachFormField(label=_("Your message"))

def __init__(self, *args, **kwargs):
subject = kwargs.pop("subject", "")
contact_person = kwargs.pop("contact_person", None)
contact_people = kwargs.pop("contact_people", [])
super().__init__(*args, **kwargs)
self.fields["contact_person"].choices = [
(person.pk, person.account.full_name()) for person in contact_people
]
self.fields["subject"].initial = subject

if contact_person:
self.fields["contact_person"].initial = contact_person.pk


class JournalContactForm(ContactPersonForm):
def __init__(self, *args, **kwargs):
warnings.warn("Use ContactPersonForm instead.")
super().__init__(*args, **kwargs)


class EditorialGroupForm(JanewayTranslationModelForm):
def __init__(self, *args, **kwargs):
next_sequence = kwargs.pop("next_sequence", None)
Expand Down
64 changes: 52 additions & 12 deletions src/core/include_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from journal import urls as journal_urls
from core import views as core_views, plugin_loader
from utils import notify
from utils import notify, views as utils_views
from press import views as press_views
from cms import views as cms_views
from submission import views as submission_views
Expand Down Expand Up @@ -120,6 +120,16 @@
press_views.IdentifierManager.as_view(),
name="press_identifier_manager",
),
re_path(
r"^press/contact/$",
press_views.contact,
name="press_contact",
),
re_path(
"press/contact/recipient/(?P<contact_person_id>\d+)/?",
press_views.contact,
name="press_contact_with_recipient",
),
# Notes
re_path(
r"^article/(?P<article_id>\d+)/note/(?P<note_id>\d+)/delete/$",
Expand Down Expand Up @@ -256,22 +266,52 @@
core_views.article_image_edit,
name="core_article_image_edit",
),
# Journal Contacts
re_path(r"^manager/contacts/$", core_views.contacts, name="core_journal_contacts"),
# Contact People
re_path(
r"^manager/contacts/add/$",
core_views.edit_contacts,
name="core_new_journal_contact",
r"^manager/contacts/$",
core_views.contact_people,
name="core_contact_people",
),
re_path(
r"^manager/contacts/(?P<contact_id>\d+)/$",
core_views.edit_contacts,
name="core_journal_contact",
r"^manager/contacts/order/$",
core_views.contact_people_reorder,
name="core_contact_people_reorder",
),
re_path(
r"^manager/contacts/order/$",
core_views.contacts_order,
name="core_journal_contacts_order",
r"^manager/contacts/search/$",
core_views.PotentialContactListView.as_view(),
name="core_contact_person_search",
),
re_path(
r"^manager/contacts/add/(?P<account_id>\d+)/$",
core_views.contact_person_create,
name="core_contact_person_create",
),
re_path(
r"^manager/contacts/(?P<contact_person_id>\d+)/$",
core_views.contact_person_update,
name="core_contact_person_update",
),
re_path(
r"^manager/contacts/(?P<contact_person_id>\d+)/delete/$",
core_views.contact_person_delete,
name="core_contact_person_delete",
),
# Contact messages
re_path(
r"^manager/contact-messages/$",
utils_views.ContactMessageListView.as_view(),
name="core_contact_messages",
),
re_path(
r"^manager/contact-messages/(?P<log_entry_id>\d+)/$",
utils_views.contact_message,
name="core_contact_message",
),
re_path(
r"^manager/contact-messages/(?P<log_entry_id>\d+)/delete/$",
utils_views.contact_message_delete,
name="core_contact_message_delete",
),
# Editorial Team
re_path(
Expand Down
1 change: 1 addition & 0 deletions src/core/janeway_global_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@

INSTALLED_APPS = [
"modeltranslation",
"django.contrib.admindocs",
"apps.JanewayAdminConfig",
"django.contrib.auth",
"django.contrib.sessions",
Expand Down
Loading