diff --git a/physionet-django/console/forms.py b/physionet-django/console/forms.py index 758a4b90c..4ac02b705 100644 --- a/physionet-django/console/forms.py +++ b/physionet-django/console/forms.py @@ -26,6 +26,7 @@ SubmissionStatus, exists_project_slug, InternalNote, + PeerReview, ) from project.validators import MAX_PROJECT_SLUG_LENGTH, validate_doi, validate_slug from user.models import CodeOfConduct, CredentialApplication, CredentialReview, User, TrainingQuestion @@ -83,6 +84,15 @@ class Meta: fields = ['content'] +class PeerReviewForm(forms.ModelForm): + class Meta: + widgets = { + 'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 6}), + } + model = PeerReview + 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 01e3ebbb6..d07e9a563 100644 --- a/physionet-django/console/templates/console/submission_info_card.html +++ b/physionet-django/console/templates/console/submission_info_card.html @@ -33,6 +33,11 @@ {{ notes|task_count_badge|safe }} +
@@ -158,6 +163,40 @@
Uploaded Documents
+ {# Reviews #} +
+

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

+ + +
+ +
+ {% csrf_token %} +
+ {{ peer_review_form.content }} +
+ +
+
+ {# Permanent Reassign #} {% if project.editor == user %}
diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index 7311353de..a48bd4ec4 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -54,7 +54,8 @@ SubmissionStatus, Topic, exists_project_slug, - InternalNote + InternalNote, + PeerReview ) from project.authorization.access import can_view_project_files from project.utility import readable_size @@ -278,6 +279,7 @@ def submission_info(request, project_slug): """ project = get_object_or_404(ActiveProject, slug=project_slug) notes = project.internal_notes.all().order_by('-created_at') + reviews = project.peer_reviews.all().order_by('-created_at') user = request.user authors, author_emails, storage_info, edit_logs, copyedit_logs, latest_version = project.info_card() @@ -285,6 +287,7 @@ def submission_info(request, project_slug): data = request.POST or None reassign_editor_form = forms.ReassignEditorForm(user, data=data) internal_note_form = forms.InternalNoteForm(data) + peer_review_form = forms.PeerReviewForm(data) embargo_form = forms.EmbargoFilesDaysForm() passphrase = '' anonymous_url = project.get_anonymous_url() @@ -331,6 +334,24 @@ def submission_info(request, project_slug): else: messages.error(request, "You are not authorized to delete this note.") return redirect(f'{request.path}?tab=notes') + if 'add_peer_review' in request.POST: + if peer_review_form.is_valid(): + review = peer_review_form.save(commit=False) + review.project = project + review.created_by = request.user + review.save() + messages.success(request, "Review added.") + peer_review_form = forms.PeerReviewForm() + return redirect(f'{request.path}?tab=reviews') + if 'delete_peer_review' in request.POST: + review_id = request.POST['review_id'] + review = get_object_or_404(PeerReview, pk=review_id, project=project) + if review.created_by == request.user: + review.delete() + messages.success(request, "Review deleted.") + else: + messages.error(request, "You are not authorized to delete this review.") + return redirect(f'{request.path}?tab=reviews') url_prefix = notification.get_url_prefix(request) bulk_url_prefix = notification.get_url_prefix(request, bulk_download=True) @@ -344,7 +365,10 @@ def submission_info(request, project_slug): 'reassign_editor_form': reassign_editor_form, 'embargo_form': embargo_form, 'notes': notes, - 'internal_note_form': internal_note_form}) + 'internal_note_form': internal_note_form, + 'reviews': reviews, + 'peer_review_form': peer_review_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 4ecd6942f..851e2d9ac 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -654,3 +654,18 @@ class InternalNote(models.Model): def __str__(self): return f"Note by {self.created_by} on {self.created_at}" + + +class PeerReview(models.Model): + """ + Allow reviews to be created for active projects. + + These reviews should only be viewable by people with editor status. + """ + project = models.ForeignKey('project.ActiveProject', on_delete=models.CASCADE, related_name='peer_reviews') + 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"Review by {self.created_by} on {self.created_at}"