diff --git a/django/library/jinja2/library/codebases/releases/edit.jinja b/django/library/jinja2/library/codebases/releases/edit.jinja index 0e78d5f30..e79fe6a38 100644 --- a/django/library/jinja2/library/codebases/releases/edit.jinja +++ b/django/library/jinja2/library/codebases/releases/edit.jinja @@ -9,7 +9,9 @@
+ data-is-live="{{ release.live }}" + data-can-edit-originals="{{ release.can_edit_originals }}" + > {% endblock %} {% block js %} diff --git a/django/library/jinja2/library/codebases/releases/retrieve.jinja b/django/library/jinja2/library/codebases/releases/retrieve.jinja index 1b4e2369e..3e91e43e7 100644 --- a/django/library/jinja2/library/codebases/releases/retrieve.jinja +++ b/django/library/jinja2/library/codebases/releases/retrieve.jinja @@ -81,6 +81,10 @@ {% endif %} {% elif release.is_under_review %}
This release is currently undergoing peer review and must remain unpublished until complete.
+ {% elif release.is_review_complete %} +
This release has undergone peer review and is currently unpublished. You can + publish it here +
{% elif release.is_draft %}
The release you are viewing is currently a draft.
{% else %} @@ -265,6 +269,11 @@
Edit {{ "Draft" if release.editable else "Metadata" }}
+ {% if not release.live and release.is_publishable %} + +
Publish
+
+ {% endif %} {% endif %} {% endwith %} diff --git a/django/library/jinja2/library/review/email/model_certified.jinja b/django/library/jinja2/library/review/email/model_certified.jinja index 8f4f5e537..f0e1d2292 100644 --- a/django/library/jinja2/library/review/email/model_certified.jinja +++ b/django/library/jinja2/library/review/email/model_certified.jinja @@ -1,10 +1,10 @@ {% set release = review.codebase_release %} {% set release_url = build_absolute_uri(release.get_absolute_url() if release.is_published else release.share_url) %} -{% set release_edit_url = build_absolute_uri(release.get_edit_url()) %} +{% set release_publish_url = build_absolute_uri(release.get_publish_url()) %} Dear {{ review.submitter.name }}, -Congratulations! The peer review of your model [{{ release.title }}]({{ release_url }}) is now complete and your release has been marked as peer reviewed. If your model was previously live the newly reviewed release has been automatically published, otherwise you may [publish it yourself]({{release_edit_url}}). An automatic DOI registration workflow that automatically assigns a DOI to all peer reviewed models is under development but if you need a DOI immediately please feel free to [contact us]({{ build_absolute_uri(slugurl('contact')) }}). +Congratulations! The peer review of your model [{{ release.title }}]({{ release_url }}) is now complete and your release has been marked as peer reviewed. Your release will remain private until you [publish it yourself]({{release_publish_url}}). An automatic DOI registration workflow that automatically assigns a DOI to all peer reviewed models is under development but if you need a DOI immediately please feel free to [contact us]({{ build_absolute_uri(slugurl('contact')) }}). Thank you for submitting your computational model for review to CoMSES Net! We hope you will be willing to serve as a reviewer of model code as well in future model peer reviews. diff --git a/django/library/jinja2/library/review/email/model_revisions_requested.jinja b/django/library/jinja2/library/review/email/model_revisions_requested.jinja index c4b29c3ae..ce1fbd183 100644 --- a/django/library/jinja2/library/review/email/model_revisions_requested.jinja +++ b/django/library/jinja2/library/review/email/model_revisions_requested.jinja @@ -8,4 +8,6 @@ The reviewer for your computational model [{{ release.title }}]({{ build_absolut {{ feedback.notes_to_author }} {% endautoescape %} +You can follow [this link]({{ build_absolute_uri(release.get_edit_url()) }}) to make changes to your model. + On behalf of the editors@comses.net, thank you for submitting your computational model(s) to CoMSES Net! Our peer review service is intended to serve the community and we hope that you find the requested changes will improve your model's accessibility and potential for reuse. If you have any questions or concerns about this process, please feel free to [contact us]({{ build_absolute_uri(slugurl("contact")) }}). diff --git a/django/library/migrations/0024_add_release_status.py b/django/library/migrations/0024_add_release_status.py index 876f99cdb..9bd8e5417 100644 --- a/django/library/migrations/0024_add_release_status.py +++ b/django/library/migrations/0024_add_release_status.py @@ -15,6 +15,12 @@ def translate_status_to_flags(apps, schema): CodebaseRelease.objects.filter(status="draft").update(draft=True, live=False) CodebaseRelease.objects.filter(status="published").update(draft=False, live=True) CodebaseRelease.objects.filter(status="unpublished").update(draft=False, live=False) + CodebaseRelease.objects.filter(status="under_review").update( + draft=False, live=False + ) + CodebaseRelease.objects.filter(status="review_complete").update( + draft=False, live=False + ) class Migration(migrations.Migration): @@ -33,6 +39,7 @@ class Migration(migrations.Migration): ("under_review", "Under review"), ("published", "Published"), ("unpublished", "Unpublished"), + ("review_complete", "Review complete"), ], default="draft", help_text="The current status of this codebase release.", diff --git a/django/library/models.py b/django/library/models.py index 65108f734..6911c94b0 100644 --- a/django/library/models.py +++ b/django/library/models.py @@ -1065,9 +1065,11 @@ class Status(models.TextChoices): # equivalent to draft but indicates that a release is under review and should not # block the normal flow of creating/publishing new releases UNDER_REVIEW = "under_review", _("Under review") + # status given after completing the review process, not editable and not live + REVIEW_COMPLETE = "review_complete", _("Review complete") # not editable and live PUBLISHED = "published", _("Published") - # not editable and not live + # indicates a release that was once published but has been unpublished UNPUBLISHED = "unpublished", _("Unpublished") date_created = models.DateTimeField(default=timezone.now) @@ -1199,6 +1201,9 @@ def get_edit_url(self): }, ) + def get_publish_url(self): + return f"{self.get_edit_url()}?publish" + def get_list_url(self): return reverse( "library:codebaserelease-list", @@ -1364,11 +1369,20 @@ def is_peer_review_requestable(self): 2. has not already been peer reviewed 3. a related PeerReview does not exist """ + return ( + self.is_publishable and not self.peer_reviewed and self.get_review() is None + ) + + @property + def is_publishable(self): + """ + Returns true if this release is ready to be published + """ try: self.validate_publishable() + return True except ValidationError: return False - return not self.peer_reviewed and self.get_review() is None @property def is_latest_version(self): @@ -1473,13 +1487,18 @@ def is_published(self): def is_under_review(self): return self.status == self.Status.UNDER_REVIEW + @property + def is_review_complete(self): + return self.status == self.Status.REVIEW_COMPLETE + @property def live(self): return self.is_published @property - def editable(self): - return self.is_draft or self.is_under_review + def can_edit_originals(self): + """return true if the original (unpublished) files are editable""" + return not self.live and not self.is_review_complete def get_status_display(self): return self.Status(self.status).label @@ -1490,6 +1509,7 @@ def get_status_color(self): self.Status.UNPUBLISHED: "gray", self.Status.UNDER_REVIEW: "danger", self.Status.PUBLISHED: "success", + self.Status.REVIEW_COMPLETE: "primary", } return COLOR_MAP.get(self.status) @@ -1813,11 +1833,9 @@ def set_complete_status(self, editor: MemberProfile): action=PeerReviewEvent.RELEASE_CERTIFIED, message="Model has been certified as peer reviewed", ) - # automatically publish the release if previous versions are live - if self.codebase_release.codebase.live: - self.codebase_release.publish() - else: - self.codebase_release.status = CodebaseRelease.Status.UNPUBLISHED + # dont un-publish releases with a review started before the new review process + if self.codebase_release.is_under_review: + self.codebase_release.status = CodebaseRelease.Status.REVIEW_COMPLETE self.codebase_release.peer_reviewed = True self.codebase_release.save() self.codebase_release.codebase.peer_reviewed = True diff --git a/django/library/serializers.py b/django/library/serializers.py index 27af3a74e..f42a75bc1 100644 --- a/django/library/serializers.py +++ b/django/library/serializers.py @@ -506,6 +506,7 @@ class CodebaseReleaseSerializer(serializers.ModelSerializer): ) license = LicenseSerializer() live = serializers.ReadOnlyField() + can_edit_originals = serializers.ReadOnlyField() os_display = serializers.ReadOnlyField(source="get_os_display") platforms = TagSerializer(many=True, source="platform_tags") programming_languages = TagSerializer(many=True) @@ -535,6 +536,7 @@ class Meta: model = CodebaseRelease fields = ( "absolute_url", + "can_edit_originals", "citation_text", "release_contributors", "date_created", diff --git a/django/library/views.py b/django/library/views.py index 587903b9c..9d659cd56 100644 --- a/django/library/views.py +++ b/django/library/views.py @@ -594,11 +594,15 @@ def has_permission(self, request, view): class NestedCodebaseReleaseUnpublishedFilesPermission(permissions.BasePermission): - def has_object_permission(self, request, view, obj): + def has_object_permission(self, request, view, obj: CodebaseRelease): if obj.live: raise DrfPermissionDenied( "Cannot access unpublished files of published release" ) + if obj.is_review_complete: + raise DrfPermissionDenied( + "Cannot modify unpublished files of a release that has been peer reviewed" + ) if request.method == "GET" and not request.user.has_perm( "library.change_codebaserelease", obj=obj ): diff --git a/frontend/src/apps/release_editor.ts b/frontend/src/apps/release_editor.ts index 7f7029da4..bbf551495 100644 --- a/frontend/src/apps/release_editor.ts +++ b/frontend/src/apps/release_editor.ts @@ -14,8 +14,13 @@ const props = extractDataParams("release-editor", [ "identifier", "reviewStatus", "isLive", + "canEditOriginals", ]); -console.log(props.reviewStatus); + +// check if ?publish is in the url, set prop, and clear from the url +const urlParams = new URLSearchParams(window.location.search); +props.showPublishModal = urlParams.has("publish"); +window.history.replaceState({}, "", window.location.pathname + window.location.hash); const app = createApp(App, props); const pinia = createPinia(); @@ -23,9 +28,11 @@ const pinia = createPinia(); const router = createRouter({ history: createWebHashHistory(), routes: [ - // only include upload route when release is unpublished - { path: "/", redirect: { name: props.isLive ? "metadata" : "upload" } }, - ...(props.isLive ? [] : [{ path: "/upload", component: UploadFormPage, name: "upload" }]), + // only include upload route when original files are editable + { path: "/", redirect: { name: props.canEditOriginals ? "upload" : "metadata" } }, + ...(props.canEditOriginals + ? [{ path: "/upload", component: UploadFormPage, name: "upload" }] + : []), { path: "/metadata", component: MetadataFormPage, name: "metadata" }, { path: "/contributors", component: ContributorsPage, name: "contributors" }, ], diff --git a/frontend/src/components/releaseEditor/App.vue b/frontend/src/components/releaseEditor/App.vue index 6127ae4aa..edfa32954 100644 --- a/frontend/src/components/releaseEditor/App.vue +++ b/frontend/src/components/releaseEditor/App.vue @@ -42,13 +42,13 @@ :files="store.files.media" /> - +
- +
@@ -77,6 +77,8 @@ const props = defineProps<{ versionNumber: string; reviewStatus: string; isLive: boolean; + canEditOriginals: boolean; + showPublishModal: boolean; }>(); const store = useReleaseEditorStore(); diff --git a/frontend/src/components/releaseEditor/ProgressSidebar.vue b/frontend/src/components/releaseEditor/ProgressSidebar.vue index b7fadb0a0..53aec2e54 100644 --- a/frontend/src/components/releaseEditor/ProgressSidebar.vue +++ b/frontend/src/components/releaseEditor/ProgressSidebar.vue @@ -2,7 +2,7 @@