From 119135f5a854e4dbda88330ab8311e6ec33f3fc5 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Thu, 11 Jul 2024 12:34:23 -0400 Subject: [PATCH 1/6] Add internal notes feature. --- physionet-django/console/forms.py | 10 ++++++ .../console/submission_info_card.html | 33 +++++++++++++++++++ physionet-django/console/views.py | 28 ++++++++++++---- .../project/modelcomponents/activeproject.py | 13 ++++++++ 4 files changed, 78 insertions(+), 6 deletions(-) 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..54660df231 100644 --- a/physionet-django/console/templates/console/submission_info_card.html +++ b/physionet-django/console/templates/console/submission_info_card.html @@ -27,6 +27,9 @@ DOIs {% endif %} +
@@ -118,6 +121,36 @@
Uploaded Documents
+ {# Notes #} +
+

Notes posted here are visible only to the editorial team. They are not visible to authors.

+ {% for note in notes %} +
  • +

    {{ note.content }}

    +

    Created by {{ note.created_by }} on {{ note.created_at }}

    +
    + {% csrf_token %} + + +
    +
  • + {% endfor %} + +
    + +
    + {% csrf_token %} +
    + {{ internal_note_form.content }} +
    + +
    +
    + {# Permanent Reassign #} {% if project.editor == user %}
    diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index 9570d9a9ee..9cc3dfb00d 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() 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,21 @@ 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) + note.delete() + messages.success(request, "Note deleted.") + 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 +339,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/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}" From 0ee5fd20a632f42752c096f68594dd77e888359a Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Thu, 11 Jul 2024 12:36:46 -0400 Subject: [PATCH 2/6] Add migration. Create InternalNote model. --- .../project/migrations/0076_internalnote.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 physionet-django/project/migrations/0076_internalnote.py 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", + ), + ), + ], + ), + ] From 64c9cda8c8d0075ab0ca40ff447ea97909a56b8a Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Thu, 11 Jul 2024 14:03:44 -0400 Subject: [PATCH 3/6] Order comments reverse chronologically by created_at date. --- physionet-django/console/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index 9cc3dfb00d..fba45aec7a 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -277,7 +277,7 @@ def submission_info(request, project_slug): View information about a project under submission """ project = get_object_or_404(ActiveProject, slug=project_slug) - notes = project.internal_notes.all() + 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() From 84754fbe0d375fb0ba9b9c1be27b4dff334fd04b Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Thu, 11 Jul 2024 14:16:29 -0400 Subject: [PATCH 4/6] Comments can only be deleted by the author of the comment. --- .../templates/console/submission_info_card.html | 16 +++++++++------- physionet-django/console/views.py | 7 +++++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/physionet-django/console/templates/console/submission_info_card.html b/physionet-django/console/templates/console/submission_info_card.html index 54660df231..9013a2d167 100644 --- a/physionet-django/console/templates/console/submission_info_card.html +++ b/physionet-django/console/templates/console/submission_info_card.html @@ -128,13 +128,15 @@
    Uploaded Documents
  • {{ note.content }}

    Created by {{ note.created_by }} on {{ note.created_at }}

    -
    - {% csrf_token %} - - -
    + {% if note.created_by == user %} +
    + {% csrf_token %} + + +
    + {% endif %}
  • {% endfor %} diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index fba45aec7a..7311353de4 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -325,8 +325,11 @@ def submission_info(request, project_slug): if 'delete_internal_note' in request.POST: note_id = request.POST['note_id'] note = get_object_or_404(InternalNote, pk=note_id, project=project) - note.delete() - messages.success(request, "Note deleted.") + 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) From a44c58e08b07689bdbc5cff421c5a4d106541101 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Thu, 11 Jul 2024 16:23:43 -0400 Subject: [PATCH 5/6] Fix mislabeled aria tags. --- .../templates/console/submission_info_card.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/physionet-django/console/templates/console/submission_info_card.html b/physionet-django/console/templates/console/submission_info_card.html index 9013a2d167..c16bbe8bdc 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 {% endif %}
    @@ -72,7 +72,7 @@

    {{ project.title }}

    {% include "project/active_submission_timeline.html" %} {# Ethics #} -
    +
    Ethics statement

    {{ project.ethics_statement|safe }}

    Uploaded Documents
    @@ -122,7 +122,7 @@
    Uploaded Documents
    {# Notes #} -
    +

    Notes posted here are visible only to the editorial team. They are not visible to authors.

    {% for note in notes %}
  • From f2a408bc16c6e3c095d58c6d412645aea14156f2 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Thu, 11 Jul 2024 17:06:29 -0400 Subject: [PATCH 6/6] Add missing list container and linebreak template tag. --- .../console/submission_info_card.html | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/physionet-django/console/templates/console/submission_info_card.html b/physionet-django/console/templates/console/submission_info_card.html index c16bbe8bdc..f9753358dd 100644 --- a/physionet-django/console/templates/console/submission_info_card.html +++ b/physionet-django/console/templates/console/submission_info_card.html @@ -124,21 +124,23 @@
    Uploaded Documents
    {# Notes #}

    Notes posted here are visible only to the editorial team. They are not visible to authors.

    - {% for note in notes %} -
  • -

    {{ note.content }}

    -

    Created by {{ note.created_by }} on {{ note.created_at }}

    - {% if note.created_by == user %} -
    - {% csrf_token %} - - -
    - {% endif %} -
  • - {% endfor %} +
      + {% for note in notes %} +
    • +

      {{ note.content|linebreaks }}

      +

      Created by {{ note.created_by }} on {{ note.created_at }}

      + {% if note.created_by == user %} +
      + {% csrf_token %} + + +
      + {% endif %} +
    • + {% endfor %} +