diff --git a/physionet-django/console/forms.py b/physionet-django/console/forms.py
index 68bee4cff2..758a4b90c1 100644
--- a/physionet-django/console/forms.py
+++ b/physionet-django/console/forms.py
@@ -25,6 +25,7 @@
PublishedPublication,
SubmissionStatus,
exists_project_slug,
+ InternalNote,
)
from project.validators import MAX_PROJECT_SLUG_LENGTH, validate_doi, validate_slug
from user.models import CodeOfConduct, CredentialApplication, CredentialReview, User, TrainingQuestion
@@ -73,6 +74,15 @@
)
+class InternalNoteForm(forms.ModelForm):
+ class Meta:
+ widgets = {
+ 'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
+ }
+ model = InternalNote
+ fields = ['content']
+
+
class AssignEditorForm(forms.Form):
"""
Assign an editor to a project under submission
diff --git a/physionet-django/console/templates/console/submission_info_card.html b/physionet-django/console/templates/console/submission_info_card.html
index 561899bea2..f9753358dd 100644
--- a/physionet-django/console/templates/console/submission_info_card.html
+++ b/physionet-django/console/templates/console/submission_info_card.html
@@ -9,7 +9,7 @@
Submission Timeline
+
Ethics statement
{{ project.ethics_statement|safe }}
Uploaded Documents
@@ -118,6 +121,40 @@
Uploaded Documents
+ {# Notes #}
+
+
Notes posted here are visible only to the editorial team. They are not visible to authors.
+
+
+
+
+
+
+
{# Permanent Reassign #}
{% if project.editor == user %}
diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py
index 9570d9a9ee..7311353de4 100644
--- a/physionet-django/console/views.py
+++ b/physionet-django/console/views.py
@@ -54,6 +54,7 @@
SubmissionStatus,
Topic,
exists_project_slug,
+ InternalNote
)
from project.authorization.access import can_view_project_files
from project.utility import readable_size
@@ -275,16 +276,15 @@ def submission_info(request, project_slug):
"""
View information about a project under submission
"""
- try:
- project = ActiveProject.objects.get(slug=project_slug)
- except ActiveProject.DoesNotExist:
- raise Http404()
+ project = get_object_or_404(ActiveProject, slug=project_slug)
+ notes = project.internal_notes.all().order_by('-created_at')
user = request.user
authors, author_emails, storage_info, edit_logs, copyedit_logs, latest_version = project.info_card()
data = request.POST or None
reassign_editor_form = forms.ReassignEditorForm(user, data=data)
+ internal_note_form = forms.InternalNoteForm(data)
embargo_form = forms.EmbargoFilesDaysForm()
passphrase = ''
anonymous_url = project.get_anonymous_url()
@@ -313,6 +313,24 @@ def submission_info(request, project_slug):
project.embargo_files_days = days
project.save()
messages.success(request, f"An embargo was set for {project.embargo_files_days} day(s)")
+ if 'add_internal_note' in request.POST:
+ if internal_note_form.is_valid():
+ note = internal_note_form.save(commit=False)
+ note.project = project
+ note.created_by = request.user
+ note.save()
+ messages.success(request, "Note added.")
+ internal_note_form = forms.InternalNoteForm()
+ return redirect(f'{request.path}?tab=notes')
+ if 'delete_internal_note' in request.POST:
+ note_id = request.POST['note_id']
+ note = get_object_or_404(InternalNote, pk=note_id, project=project)
+ if note.created_by == request.user:
+ note.delete()
+ messages.success(request, "Note deleted.")
+ else:
+ messages.error(request, "You are not authorized to delete this note.")
+ return redirect(f'{request.path}?tab=notes')
url_prefix = notification.get_url_prefix(request)
bulk_url_prefix = notification.get_url_prefix(request, bulk_download=True)
@@ -324,8 +342,9 @@ def submission_info(request, project_slug):
'anonymous_url': anonymous_url, 'url_prefix': url_prefix,
'bulk_url_prefix': bulk_url_prefix,
'reassign_editor_form': reassign_editor_form,
- 'embargo_form': embargo_form})
-
+ 'embargo_form': embargo_form,
+ 'notes': notes,
+ 'internal_note_form': internal_note_form})
@handling_editor
def edit_submission(request, project_slug, *args, **kwargs):
diff --git a/physionet-django/project/migrations/0076_internalnote.py b/physionet-django/project/migrations/0076_internalnote.py
new file mode 100644
index 0000000000..5cb0892862
--- /dev/null
+++ b/physionet-django/project/migrations/0076_internalnote.py
@@ -0,0 +1,46 @@
+# Generated by Django 4.1.13 on 2024-07-11 16:35
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ("project", "0075_publishedreference_url"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="InternalNote",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("created_at", models.DateTimeField(auto_now_add=True)),
+ ("content", models.TextField()),
+ (
+ "created_by",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ (
+ "project",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="internal_notes",
+ to="project.activeproject",
+ ),
+ ),
+ ],
+ ),
+ ]
diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py
index 113ffed5db..4ecd6942fd 100644
--- a/physionet-django/project/modelcomponents/activeproject.py
+++ b/physionet-django/project/modelcomponents/activeproject.py
@@ -641,3 +641,16 @@ def is_editable_by(self, user):
editor_copyediting = self.copyeditable() and user == self.editor
return author_submitting or editor_copyediting
+
+
+class InternalNote(models.Model):
+ """
+ Allow notes to be created for active projects. These notes should only be viewable by people with editor status.
+ """
+ project = models.ForeignKey('project.ActiveProject', on_delete=models.CASCADE, related_name='internal_notes')
+ created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
+ created_at = models.DateTimeField(auto_now_add=True)
+ content = models.TextField()
+
+ def __str__(self):
+ return f"Note by {self.created_by} on {self.created_at}"