diff --git a/.env.example b/.env.example index 0f0ca32848..50220306f4 100644 --- a/.env.example +++ b/.env.example @@ -170,6 +170,10 @@ GRADIENT_85 = 'rgba(42, 47, 52, 0.85)' # maximum number of emails that can be associated to a user model MAX_EMAILS_PER_USER = 10 +# maximum number of active projects that can be created by a submitting author at any time. +# if MAX_SUBMITTABLE_PROJECTS is reached, the user must wait for a project to be archived or published before starting another. +MAX_SUBMITTABLE_PROJECTS = 10 + # Max training report size in bytes MAX_TRAINING_REPORT_UPLOAD_SIZE = 1048576 ENABLE_LIGHTWAVE=True diff --git a/physionet-django/console/forms.py b/physionet-django/console/forms.py index a24cc5c876..e1ec6393a4 100644 --- a/physionet-django/console/forms.py +++ b/physionet-django/console/forms.py @@ -223,9 +223,6 @@ def save(self): # Reject if edit_log.decision == 0: project.reject() - # Have to reload this object which is changed by the reject - # function - edit_log = EditLog.objects.get(id=edit_log.id) # Resubmit with revisions elif edit_log.decision == 1: project.submission_status = SubmissionStatus.NEEDS_RESUBMISSION diff --git a/physionet-django/console/templates/console/console_navbar.html b/physionet-django/console/templates/console/console_navbar.html index c282399175..e25f14d351 100644 --- a/physionet-django/console/templates/console/console_navbar.html +++ b/physionet-django/console/templates/console/console_navbar.html @@ -20,7 +20,7 @@ - {% if perms.project.change_activeproject or perms.project.change_publishedproject or perms.project.change_archivedproject %} + {% if perms.project.change_activeproject or perms.project.change_publishedproject %}
Demo abstract
", - "background": "Demo background
", - "methods": "Demo methods
", - "content_description": "Demo content description
", - "usage_notes": "Demo usage notes
", - "installation": "Demo installation
", - "acknowledgements": "Demo acknowledgements
", - "conflicts_of_interest": "Demo conflicts of interest
", - "version": "1.0", - "release_notes": "Demo release notes
", - "short_description": "", - "access_policy": 0, - "license": null, - "dua": null, - "project_home_page": "", - "core_project": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", - "editor": null, - "submission_datetime": "2020-04-07T15:54:54.860Z", - "author_comments": "", - "editor_assignment_datetime": "2020-04-13T16:06:22.892Z", - "revision_request_datetime": null, - "resubmission_datetime": null, - "editor_accept_datetime": "2020-04-20T16:06:55.526Z", - "copyedit_completion_datetime": "2020-04-21T16:07:36.681Z", - "author_approval_datetime": "2020-04-22T16:08:02Z", - "creation_datetime": "2020-04-10T15:54:54.860Z", - "modified_datetime": "2020-05-11T16:07:36.681Z", - "is_new_version": false, - "slug": "t2ASGLbIBoWaTJvPrM2A", - "latest_reminder": null, - "doi": null, - "archive_datetime": "2020-05-21T16:07:36.681Z", - "archive_reason": 3, - "parent_projects": [], - "programming_languages": [], - "allow_file_downloads": true, - "ethics_statement": "The authors declare no ethics concerns." - } -}, { "model": "project.activeproject", "pk": 1, @@ -1233,6 +1188,51 @@ "required_trainings": [1] } }, +{ + "model": "project.activeproject", + "pk": 9, + "fields": { + "resource_type": 1, + "title": "Failed demo software for parsing clinical notes", + "abstract": "Demo abstract
", + "background": "Demo background
", + "methods": "Demo methods
", + "content_description": "Demo content description
", + "usage_notes": "Demo usage notes
", + "installation": "Demo installation
", + "acknowledgements": "Demo acknowledgements
", + "conflicts_of_interest": "Demo conflicts of interest
", + "version": "2.0", + "release_notes": "This is version 2.0
", + "short_description": "Demo short description", + "access_policy": 0, + "license": 9, + "dua": null, + "project_home_page": "", + "core_project": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", + "editor": null, + "submission_datetime": "2020-04-10T16:08:53.263Z", + "author_comments": "", + "editor_assignment_datetime": "2020-04-10T16:09:06.581Z", + "revision_request_datetime": null, + "resubmission_datetime": null, + "editor_accept_datetime": null, + "copyedit_completion_datetime": null, + "author_approval_datetime": null, + "creation_datetime": "2020-04-10T16:08:49.323Z", + "modified_datetime": "2020-04-10T16:08:49.323Z", + "is_new_version": false, + "slug": "t2ASGLbIBoWaTJvPrM2A", + "latest_reminder": null, + "doi": null, + "submission_status": 5, + "parent_projects": [], + "programming_languages": [], + "allow_file_downloads": true, + "ethics_statement": "The authors declare no ethics concerns.", + "required_trainings": [1] + } +}, { "model": "project.publishedproject", "pk": 1, diff --git a/physionet-django/project/migrations/0066_migrate_references_to_use_order.py b/physionet-django/project/migrations/0066_migrate_references_to_use_order.py index aa0176353c..e273aeb381 100644 --- a/physionet-django/project/migrations/0066_migrate_references_to_use_order.py +++ b/physionet-django/project/migrations/0066_migrate_references_to_use_order.py @@ -1,8 +1,10 @@ from django.db import migrations, models -from project.models import PublishedProject, ActiveProject, ArchivedProject, LegacyProject def migrate_forward(apps, schema_editor): + PublishedProject = apps.get_model("project", "PublishedProject") + ActiveProject = apps.get_model("project", "ActiveProject") + ArchivedProject = apps.get_model("project", "ArchivedProject") for project in PublishedProject.objects.all(): refs = project.references.all().order_by('id') diff --git a/physionet-django/project/migrations/0073_activeproject_archive_datetime.py b/physionet-django/project/migrations/0073_activeproject_archive_datetime.py new file mode 100644 index 0000000000..4260825436 --- /dev/null +++ b/physionet-django/project/migrations/0073_activeproject_archive_datetime.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1.10 on 2023-12-01 07:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("project", "0072_aws"), + ] + + operations = [ + migrations.AddField( + model_name="activeproject", + name="archive_datetime", + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index ad9be2765b..c9f8b82c87 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -19,7 +19,6 @@ from console.tasks import associated_task from physionet.settings.base import StorageTypes from project.modelcomponents.access import AccessPolicy -from project.modelcomponents.archivedproject import ArchivedProject from project.modelcomponents.authors import PublishedAffiliation, PublishedAuthor from project.modelcomponents.metadata import ( Contact, @@ -88,6 +87,11 @@ class SubmissionStatus(IntEnum): ready, the submitting author may submit the project, which moves it to NEEDS_ASSIGNMENT. + 5: ARCHIVED + -------------- + The project has been archived. In this stage, the project cannot be + edited. To recover the project, it must be returned to UNSUBMITTED status. + 10: NEEDS_ASSIGNMENT ("Awaiting Editor Assignment") --------------------------------------------------- The project has been submitted, but has no editor assigned. A @@ -99,8 +103,8 @@ class SubmissionStatus(IntEnum): An editor has been assigned and needs to review the project. The editor may accept the project, which moves it to NEEDS_COPYEDIT; may request resubmission, which moves the project to - NEEDS_RESUBMISSION; or may reject the project, which deletes the - ActiveProject and transfers its content to an ArchivedProject. + NEEDS_RESUBMISSION; or may reject the project, which sets the + status of the ActiveProject to "Archived". 30: NEEDS_RESUBMISSION ("Awaiting Author Revisions") ------------------------------------------------- @@ -130,6 +134,7 @@ class SubmissionStatus(IntEnum): to a PublishedProject. """ UNSUBMITTED = 0 + ARCHIVED = 5 NEEDS_ASSIGNMENT = 10 NEEDS_DECISION = 20 NEEDS_RESUBMISSION = 30 @@ -150,7 +155,6 @@ class ActiveProject(Metadata, UnpublishedProject, SubmissionInfo): submission_status = models.PositiveSmallIntegerField(default=0) # Max number of active submitting projects a user is allowed to have - MAX_SUBMITTING_PROJECTS = 10 INDIVIDUAL_FILE_SIZE_LIMIT = 10 * 1024**3 # Subdirectory (under self.files.file_root) where files are stored @@ -196,6 +200,7 @@ class ActiveProject(Metadata, UnpublishedProject, SubmissionInfo): SUBMISSION_STATUS_LABELS = { SubmissionStatus.UNSUBMITTED: 'Not submitted.', + SubmissionStatus.ARCHIVED: 'Archived.', SubmissionStatus.NEEDS_ASSIGNMENT: 'Awaiting editor assignment.', SubmissionStatus.NEEDS_DECISION: 'Awaiting editor decision.', SubmissionStatus.NEEDS_RESUBMISSION: 'Revisions requested.', @@ -290,73 +295,16 @@ def copyeditable(self): if self.submission_status == SubmissionStatus.NEEDS_COPYEDIT: return True - def archive(self, archive_reason): + def archive(self, archive_reason, clear_files=False): """ - Archive the project. Create an ArchivedProject object, copy over - the fields, and delete this object + Archive the project. Sets the status of the project to "Archived" object. """ - archived_project = ArchivedProject(archive_reason=archive_reason, - slug=self.slug) - - modified_datetime = self.modified_datetime + self.submission_status = SubmissionStatus.ARCHIVED + self.archive_datetime = timezone.now() + self.save() - # Direct copy over fields - for attr in [f.name for f in Metadata._meta.fields] + [f.name for f in SubmissionInfo._meta.fields]: - setattr(archived_project, attr, getattr(self, attr)) - - archived_project.save() - - # Redirect the related objects - for reference in self.references.all(): - reference.project = archived_project - reference.save() - for publication in self.publications.all(): - publication.project = archived_project - publication.save() - for topic in self.topics.all(): - topic.project = archived_project - topic.save() - for author in self.authors.all(): - author.project = archived_project - author.save() - for edit_log in self.edit_logs.all(): - edit_log.project = archived_project - edit_log.save() - for copyedit_log in self.copyedit_logs.all(): - copyedit_log.project = archived_project - copyedit_log.save() - for parent_project in self.parent_projects.all(): - archived_project.parent_projects.add(parent_project) - - UploadedDocument.objects.filter( - object_id=self.pk, content_type=ContentType.objects.get_for_model(ActiveProject) - ).update(object_id=archived_project.pk, content_type=ContentType.objects.get_for_model(ArchivedProject)) - - if self.resource_type.id == 1: - languages = self.programming_languages.all() - if languages: - archived_project.programming_languages.add(*list(languages)) - - # Voluntary delete - if archive_reason == 1: + if clear_files: self.clear_files() - else: - # Move over files - self.files.rename(self.file_root(), archived_project.file_root()) - - # Copy the ActiveProject timestamp to the ArchivedProject. - # Since this is an auto_now field, save() doesn't allow - # setting an arbitrary value. - queryset = ArchivedProject.objects.filter(id=archived_project.id) - queryset.update(modified_datetime=modified_datetime) - - return self.delete() - - def fake_delete(self): - """ - Appear to delete this project. Actually archive it. - """ - self.archive(archive_reason=1) def check_integrity(self): """ @@ -469,7 +417,7 @@ def reject(self): """ Reject a project under submission """ - self.archive(archive_reason=3) + self.archive(archive_reason=0) def is_resubmittable(self): """ diff --git a/physionet-django/project/modelcomponents/archivedproject.py b/physionet-django/project/modelcomponents/archivedproject.py index 7c65d50204..5037c3a0b5 100644 --- a/physionet-django/project/modelcomponents/archivedproject.py +++ b/physionet-django/project/modelcomponents/archivedproject.py @@ -10,6 +10,9 @@ class ArchivedProject(Metadata, UnpublishedProject, SubmissionInfo): """ + THIS MODEL WILL BE DEPRECATED. INSTEAD, USE ACTIVEPROJECT + WITH SUBMISSIONSTATUS=ARCHIVED. + An archived project. Created when (maps to archive_reason): 1. A user chooses to 'delete' their ActiveProject. 2. An ActiveProject is not submitted for too long. diff --git a/physionet-django/project/modelcomponents/authors.py b/physionet-django/project/modelcomponents/authors.py index 1e96bcf3ba..3eab9d134d 100644 --- a/physionet-django/project/modelcomponents/authors.py +++ b/physionet-django/project/modelcomponents/authors.py @@ -102,7 +102,7 @@ def __str__(self): class Author(BaseAuthor): """ - The author model for ArchivedProject/ActiveProject + The author model for ActiveProject """ content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() diff --git a/physionet-django/project/modelcomponents/coreproject.py b/physionet-django/project/modelcomponents/coreproject.py index 2cddf9380a..70a7937fb6 100644 --- a/physionet-django/project/modelcomponents/coreproject.py +++ b/physionet-django/project/modelcomponents/coreproject.py @@ -4,7 +4,6 @@ from django.db import models from project.modelcomponents.activeproject import ActiveProject -from project.modelcomponents.archivedproject import ArchivedProject from project.modelcomponents.publishedproject import PublishedProject @@ -87,6 +86,4 @@ def exists_project_slug(slug): Whether the slug has been taken by an existing project of any kind. """ - return bool(ActiveProject.objects.filter(slug=slug) - or ArchivedProject.objects.filter(slug=slug) - or PublishedProject.objects.filter(slug=slug)) + return bool(ActiveProject.objects.filter(slug=slug) or PublishedProject.objects.filter(slug=slug)) diff --git a/physionet-django/project/modelcomponents/metadata.py b/physionet-django/project/modelcomponents/metadata.py index 37eccef5c0..f0abb1e1ad 100644 --- a/physionet-django/project/modelcomponents/metadata.py +++ b/physionet-django/project/modelcomponents/metadata.py @@ -25,9 +25,9 @@ class Metadata(models.Model): """ Visible content of a published or unpublished project. - Every project (ActiveProject, PublishedProject, and - ArchivedProject) inherits from this class as well as - SubmissionInfo. The difference is that the fields of this class + Every project (ActiveProject, PublishedProject) inherits + from this class as well as SubmissionInfo. + The difference is that the fields of this class contain public information that will be shown on the published project pages; SubmissionInfo contains internal information about the publication process. @@ -534,7 +534,7 @@ def citation_text_all(self): class Topic(models.Model): """ - Topic information to tag ActiveProject/ArchivedProject + Topic information to tag ActiveProject """ content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() @@ -576,7 +576,7 @@ def __str__(self): class Reference(models.Model): """ - Reference field for ActiveProject/ArchivedProject + Reference field for ActiveProject """ content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() @@ -643,7 +643,7 @@ class Meta: class Publication(BasePublication): """ - Publication for ArchivedProject/ActiveProject + Publication for ActiveProject """ content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() diff --git a/physionet-django/project/modelcomponents/submission.py b/physionet-django/project/modelcomponents/submission.py index 1117f9b6d3..fce3113378 100644 --- a/physionet-django/project/modelcomponents/submission.py +++ b/physionet-django/project/modelcomponents/submission.py @@ -188,8 +188,8 @@ class SubmissionInfo(models.Model): """ Submission information, inherited by all projects. - Every project (ActiveProject, PublishedProject, and - ArchivedProject) inherits from this class as well as Metadata. + Every project (ActiveProject, PublishedProject) inherits + from this class as well as Metadata. The difference is that the fields of this class contain internal information about the publication process; Metadata contains the public information that will be shown on the published project diff --git a/physionet-django/project/modelcomponents/unpublishedproject.py b/physionet-django/project/modelcomponents/unpublishedproject.py index b05566e76a..a82e07caea 100644 --- a/physionet-django/project/modelcomponents/unpublishedproject.py +++ b/physionet-django/project/modelcomponents/unpublishedproject.py @@ -15,7 +15,7 @@ class UnpublishedProject(models.Model): """ - Abstract model inherited by ArchivedProject/ActiveProject + Abstract model inherited by ActiveProject """ # Date and time that the project's content was modified. @@ -32,7 +32,7 @@ class UnpublishedProject(models.Model): references = GenericRelation('project.Reference') publications = GenericRelation('project.Publication') topics = GenericRelation('project.Topic') - + archive_datetime = models.DateTimeField(null=True, blank=True) class Meta: abstract = True diff --git a/physionet-django/project/templates/project/active_submission_timeline.html b/physionet-django/project/templates/project/active_submission_timeline.html index 9c6a09943d..04bfa3b829 100644 --- a/physionet-django/project/templates/project/active_submission_timeline.html +++ b/physionet-django/project/templates/project/active_submission_timeline.html @@ -130,6 +130,17 @@: The editor rejected the submission.
+The editor rejected the submission.
{% elif e.decision == 1 %}The editor requested a resubmission with revisions.
{% elif e.decision == 2 %} diff --git a/physionet-django/project/templates/project/static_submission_timeline.html b/physionet-django/project/templates/project/static_submission_timeline.html index 06e6d069e1..52db7566d8 100644 --- a/physionet-django/project/templates/project/static_submission_timeline.html +++ b/physionet-django/project/templates/project/static_submission_timeline.html @@ -51,26 +51,23 @@ {% endfor %} {% endif %} - - {# There may have been any number of submissions #} - {% for e in edit_logs %} - {% if e.is_resubmission %} -The project was resubmitted for review.
- {% if e.author_comments %} -The submitting author included the following comments:
- {{ e.author_comments|linebreaks }} - {% endif %} -The editor comments regarding the submission are as follows:
- {{ e.editor_comments|linebreaks }} -The editor comments regarding the submission are as follows:
+ {{ e.editor_comments|linebreaks }}The project was resubmitted for review.
+ {% if e.author_comments %} +The submitting author included the following comments:
+ {{ e.author_comments|linebreaks }} + {% endif %} +