From 35a69289bac5d1bd2e653f6406cb7a083a704f0c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Caux?= <git@jscaux.org>
Date: Fri, 25 Feb 2022 22:23:08 +0100
Subject: [PATCH] Rework Publication and Submission API

---
 scipost_django/api/urls.py                    | 14 +++-
 .../comments/api/serializers/__init__.py      |  5 ++
 .../comments/api/serializers/comment.py       | 35 +++++++++
 .../journals/api/filtersets/__init__.py       |  5 +-
 .../journals/api/filtersets/publication.py    | 33 ++++++++
 .../journals/api/serializers/__init__.py      |  5 +-
 .../journals/api/serializers/publication.py   | 20 +++++
 .../journals/api/viewsets/__init__.py         |  5 +-
 .../journals/api/viewsets/publication.py      | 44 ++++++++++-
 .../preprints/api/serializers/__init__.py     |  5 ++
 .../preprints/api/serializers/preprint.py     | 18 +++++
 .../submissions/api/filtersets/__init__.py    |  5 +-
 .../submissions/api/filtersets/submission.py  | 30 ++++++++
 .../submissions/api/serializers/__init__.py   |  9 ++-
 .../submissions/api/serializers/report.py     | 38 ++++++++++
 .../submissions/api/serializers/submission.py | 76 ++++++++++++++++++-
 .../api/serializers/submission_event.py       | 19 +++++
 .../submissions/api/viewsets/__init__.py      |  5 +-
 .../submissions/api/viewsets/submission.py    | 30 +++++++-
 19 files changed, 388 insertions(+), 13 deletions(-)
 create mode 100644 scipost_django/comments/api/serializers/__init__.py
 create mode 100644 scipost_django/comments/api/serializers/comment.py
 create mode 100644 scipost_django/preprints/api/serializers/__init__.py
 create mode 100644 scipost_django/preprints/api/serializers/preprint.py
 create mode 100644 scipost_django/submissions/api/serializers/report.py
 create mode 100644 scipost_django/submissions/api/serializers/submission_event.py

diff --git a/scipost_django/api/urls.py b/scipost_django/api/urls.py
index 81669f12d..83fbc8ae3 100644
--- a/scipost_django/api/urls.py
+++ b/scipost_django/api/urls.py
@@ -8,12 +8,16 @@ from rest_framework import routers
 
 # journals
 from journals.api.viewsets import (
+    PublicationPublicAPIViewSet,
     PublicationPublicSearchAPIViewSet,
     PubFractionPublicAPIViewSet,
 )
 
 # submissions
-from submissions.api.viewsets import SubmissionPublicSearchAPIViewSet
+from submissions.api.viewsets import (
+    SubmissionPublicAPIViewSet,
+    SubmissionPublicSearchAPIViewSet,
+)
 
 # organizations
 from organizations.api.viewsets import (
@@ -39,14 +43,20 @@ app_name = "api"
 
 router = routers.SimpleRouter()
 
-# search (Vue) routes
+# search (Vue-based) routes
 router.register("search/publications", PublicationPublicSearchAPIViewSet)
 router.register("search/submissions", SubmissionPublicSearchAPIViewSet)
 
+#############################
+# publicly-accessible routes
+#############################
+
 # journals
+router.register("publications", PublicationPublicAPIViewSet)
 router.register("pubfractions", PubFractionPublicAPIViewSet)
 
 # submissions
+router.register("submissions", SubmissionPublicAPIViewSet)
 
 # organizations
 router.register("organizations", OrganizationPublicAPIViewSet)
diff --git a/scipost_django/comments/api/serializers/__init__.py b/scipost_django/comments/api/serializers/__init__.py
new file mode 100644
index 000000000..595828482
--- /dev/null
+++ b/scipost_django/comments/api/serializers/__init__.py
@@ -0,0 +1,5 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from .comment import CommentPublicSerializer
diff --git a/scipost_django/comments/api/serializers/comment.py b/scipost_django/comments/api/serializers/comment.py
new file mode 100644
index 000000000..79433a8ba
--- /dev/null
+++ b/scipost_django/comments/api/serializers/comment.py
@@ -0,0 +1,35 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from rest_framework import serializers
+
+from ...models import Comment
+
+
+class CommentPublicSerializer(serializers.ModelSerializer):
+    status = serializers.CharField(source="get_status_display")
+    author = serializers.SerializerMethodField()
+    nested_comments = serializers.SerializerMethodField()
+    url = serializers.URLField(source="get_absolute_url")
+
+    class Meta:
+        model = Comment
+        fields = [
+            "status",
+            "author",
+            "is_author_reply",
+            "file_attachment",
+            "date_submitted",
+            "doi_string",
+            "citation",
+            "url",
+            "nested_comments",
+        ]
+
+    def get_author(self, obj):
+        return obj.get_author_str()
+
+    def get_nested_comments(self, obj):
+        comments = obj.comments.vetted()
+        return CommentPublicSerializer(comments, many=True, read_only=True).data
diff --git a/scipost_django/journals/api/filtersets/__init__.py b/scipost_django/journals/api/filtersets/__init__.py
index 2fd7ba0e6..4076babe7 100644
--- a/scipost_django/journals/api/filtersets/__init__.py
+++ b/scipost_django/journals/api/filtersets/__init__.py
@@ -2,6 +2,9 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
-from .publication import PublicationPublicSearchAPIFilterSet
+from .publication import (
+    PublicationPublicAPIFilterSet,
+    PublicationPublicSearchAPIFilterSet,
+)
 
 from .pubfraction import PubFractionPublicAPIFilterSet
diff --git a/scipost_django/journals/api/filtersets/publication.py b/scipost_django/journals/api/filtersets/publication.py
index bf209fb71..4fe8d3f38 100644
--- a/scipost_django/journals/api/filtersets/publication.py
+++ b/scipost_django/journals/api/filtersets/publication.py
@@ -7,6 +7,39 @@ from django_filters import rest_framework as df_filters
 from ...models import Publication
 
 
+class PublicationPublicAPIFilterSet(df_filters.FilterSet):
+    class Meta:
+        model = Publication
+        fields = {
+            "title": ["icontains", "contains", "istartswith", "iregex", "regex"],
+            "author_list": ["icontains", "contains", "iregex", "regex"],
+            "abstract": ["icontains", "contains", "iregex", "regex"],
+            "publication_date": [
+                "year",
+                "month",
+                "exact",
+                "year__gte",
+                "year__lte",
+                "year__range",
+                "gte",
+                "lte",
+                "range",
+            ],
+            "doi_label": [
+                "icontains",
+            ],
+            "acad_field__name": [
+                "icontains",
+            ],
+            "specialties__name": [
+                "icontains",
+            ],
+            "topics__name": [
+                "icontains",
+            ],
+        }
+
+
 class PublicationPublicSearchAPIFilterSet(df_filters.FilterSet):
     class Meta:
         model = Publication
diff --git a/scipost_django/journals/api/serializers/__init__.py b/scipost_django/journals/api/serializers/__init__.py
index 7991aad30..b564fb91b 100644
--- a/scipost_django/journals/api/serializers/__init__.py
+++ b/scipost_django/journals/api/serializers/__init__.py
@@ -2,6 +2,9 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
-from .publication import PublicationPublicSearchSerializer
+from .publication import (
+    PublicationPublicSerializer,
+    PublicationPublicSearchSerializer,
+)
 
 from .pubfraction import PubFractionPublicSerializer
diff --git a/scipost_django/journals/api/serializers/publication.py b/scipost_django/journals/api/serializers/publication.py
index 238c8af02..c202fc5a6 100644
--- a/scipost_django/journals/api/serializers/publication.py
+++ b/scipost_django/journals/api/serializers/publication.py
@@ -8,6 +8,26 @@ from api.serializers import DynamicFieldsModelSerializer
 from ...models import Publication
 
 
+class PublicationPublicSerializer(DynamicFieldsModelSerializer):
+    url = serializers.URLField(source="get_absolute_url")
+    accepted_submission = serializers.SerializerMethodField()
+
+    class Meta:
+        model = Publication
+        fields = [
+            "url",
+            "title",
+            "author_list",
+            "abstract",
+            "doi_label",
+            "publication_date",
+            "accepted_submission",
+        ]
+
+    def get_accepted_submission(self, obj):
+        return obj.accepted_submission.get_absolute_url()
+
+
 class PublicationPublicSearchSerializer(DynamicFieldsModelSerializer):
     url = serializers.URLField(source="get_absolute_url")
 
diff --git a/scipost_django/journals/api/viewsets/__init__.py b/scipost_django/journals/api/viewsets/__init__.py
index 80af7492d..e7344e94a 100644
--- a/scipost_django/journals/api/viewsets/__init__.py
+++ b/scipost_django/journals/api/viewsets/__init__.py
@@ -2,6 +2,9 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
-from .publication import PublicationPublicSearchAPIViewSet
+from .publication import (
+    PublicationPublicAPIViewSet,
+    PublicationPublicSearchAPIViewSet,
+)
 
 from .pubfraction import PubFractionPublicAPIViewSet
diff --git a/scipost_django/journals/api/viewsets/publication.py b/scipost_django/journals/api/viewsets/publication.py
index f7df91e8e..11f53b68b 100644
--- a/scipost_django/journals/api/viewsets/publication.py
+++ b/scipost_django/journals/api/viewsets/publication.py
@@ -11,10 +11,50 @@ from api.viewsets.mixins import FilteringOptionsActionMixin
 
 from journals.models import Publication
 from journals.regexes import PUBLICATION_DOI_LABEL_REGEX
-from journals.api.filtersets import PublicationPublicSearchAPIFilterSet
-from journals.api.serializers import PublicationPublicSearchSerializer
+from journals.api.filtersets import (
+    PublicationPublicAPIFilterSet,
+    PublicationPublicSearchAPIFilterSet,
+)
+from journals.api.serializers import (
+    PublicationPublicSerializer,
+    PublicationPublicSearchSerializer,
+)
 
 
+class PublicationPublicAPIViewSet(
+    FilteringOptionsActionMixin, ExtraFilteredReadOnlyModelViewSet
+):
+    queryset = Publication.objects.published()
+    permission_classes = [
+        AllowAny,
+    ]
+    serializer_class = PublicationPublicSerializer
+    lookup_field = "doi_label"
+    lookup_value_regex = PUBLICATION_DOI_LABEL_REGEX
+    search_fields = ["title", "author_list", "abstract", "doi_label"]
+    ordering_fields = [
+        "publication_date",
+    ]
+    filterset_class = PublicationPublicAPIFilterSet
+    extra_filters = {
+        "journal__name": {
+            "fields": [
+                "in_journal__name",
+                "in_issue__in_journal__name",
+                "in_issue__in_volume__in_journal__name",
+            ],
+            "lookups": ["icontains", "istartswith", "iexact", "exact"],
+        }
+    }
+    default_filtering_fields = [
+        "title__icontains",
+        "author_list__icontains",
+        "abstract__icontains",
+        "doi_label__icontains",
+    ]
+
+
+# For Vue-based search
 class PublicationPublicSearchAPIViewSet(
     FilteringOptionsActionMixin, ExtraFilteredReadOnlyModelViewSet
 ):
diff --git a/scipost_django/preprints/api/serializers/__init__.py b/scipost_django/preprints/api/serializers/__init__.py
new file mode 100644
index 000000000..7256c0db4
--- /dev/null
+++ b/scipost_django/preprints/api/serializers/__init__.py
@@ -0,0 +1,5 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from .preprint import PreprintPublicSerializer
diff --git a/scipost_django/preprints/api/serializers/preprint.py b/scipost_django/preprints/api/serializers/preprint.py
new file mode 100644
index 000000000..106b817b1
--- /dev/null
+++ b/scipost_django/preprints/api/serializers/preprint.py
@@ -0,0 +1,18 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from rest_framework import serializers
+
+from ...models import Preprint
+
+
+class PreprintPublicSerializer(serializers.ModelSerializer):
+    url = serializers.URLField(source="get_absolute_url")
+
+    class Meta:
+        model = Preprint
+        fields = [
+            "identifier_w_vn_nr",
+            "url",
+        ]
diff --git a/scipost_django/submissions/api/filtersets/__init__.py b/scipost_django/submissions/api/filtersets/__init__.py
index b0b80ee43..7103ca96a 100644
--- a/scipost_django/submissions/api/filtersets/__init__.py
+++ b/scipost_django/submissions/api/filtersets/__init__.py
@@ -2,4 +2,7 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
-from .submission import SubmissionPublicSearchAPIFilterSet
+from .submission import (
+    SubmissionPublicAPIFilterSet,
+    SubmissionPublicSearchAPIFilterSet,
+)
diff --git a/scipost_django/submissions/api/filtersets/submission.py b/scipost_django/submissions/api/filtersets/submission.py
index 12209bd5f..e3410595c 100644
--- a/scipost_django/submissions/api/filtersets/submission.py
+++ b/scipost_django/submissions/api/filtersets/submission.py
@@ -7,6 +7,36 @@ from django_filters import rest_framework as df_filters
 from submissions.models import Submission
 
 
+class SubmissionPublicAPIFilterSet(df_filters.FilterSet):
+    class Meta:
+        model = Submission
+        fields = {
+            "title": ["icontains", "contains", "istartswith", "iregex", "regex"],
+            "author_list": ["icontains", "contains", "iregex", "regex"],
+            "abstract": ["icontains", "contains", "iregex", "regex"],
+            "submission_date": [
+                "date__year",
+                "date__month",
+                "date__exact",
+                "date__year__gte",
+                "date__year__lte",
+                "date__year__range",
+                "date__gte",
+                "date__lte",
+                "date__range",
+            ],
+            "acad_field__name": [
+                "icontains",
+            ],
+            "specialties__name": [
+                "icontains",
+            ],
+            "topics__name": [
+                "icontains",
+            ],
+        }
+
+
 class SubmissionPublicSearchAPIFilterSet(df_filters.FilterSet):
     class Meta:
         model = Submission
diff --git a/scipost_django/submissions/api/serializers/__init__.py b/scipost_django/submissions/api/serializers/__init__.py
index 12c9478b8..19f7ff105 100644
--- a/scipost_django/submissions/api/serializers/__init__.py
+++ b/scipost_django/submissions/api/serializers/__init__.py
@@ -2,4 +2,11 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
-from .submission import SubmissionPublicSearchSerializer
+from .report import ReportPublicSerializer
+
+from .submission_event import SubmissionEventPublicSerializer
+
+from .submission import (
+    SubmissionPublicSerializer,
+    SubmissionPublicSearchSerializer,
+)
diff --git a/scipost_django/submissions/api/serializers/report.py b/scipost_django/submissions/api/serializers/report.py
new file mode 100644
index 000000000..11e1c5ee1
--- /dev/null
+++ b/scipost_django/submissions/api/serializers/report.py
@@ -0,0 +1,38 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from rest_framework import serializers
+
+from ...models import Report
+
+from comments.api.serializers import CommentPublicSerializer
+
+
+class ReportPublicSerializer(serializers.ModelSerializer):
+    author = serializers.SerializerMethodField()
+    url = serializers.URLField(source="get_absolute_url")
+    comments = serializers.SerializerMethodField()
+
+    class Meta:
+        model = Report
+        fields = [
+            "status",
+            "report_type",
+            "report_nr",
+            "invited",
+            "author",
+            "doi_string",
+            "date_submitted",
+            "url",
+            "comments",
+        ]
+
+    def get_author(self, obj):
+        if not obj.anonymous:
+            return str(obj.author)
+        return "Anonymous"
+
+    def get_comments(self, obj):
+        comments = obj.comments.vetted()
+        return CommentPublicSerializer(comments, many=True, read_only=True).data
diff --git a/scipost_django/submissions/api/serializers/submission.py b/scipost_django/submissions/api/serializers/submission.py
index 41ea65172..2413a95dd 100644
--- a/scipost_django/submissions/api/serializers/submission.py
+++ b/scipost_django/submissions/api/serializers/submission.py
@@ -4,7 +4,81 @@ __license__ = "AGPL v3"
 
 from rest_framework import serializers
 
-from submissions.models import Submission
+from submissions.models import Submission, Report
+from submissions.api.serializers import (
+    ReportPublicSerializer,
+    SubmissionEventPublicSerializer,
+)
+
+from comments.models import Comment
+from comments.api.serializers import CommentPublicSerializer
+from preprints.api.serializers import PreprintPublicSerializer
+
+
+class SubmissionPublicSerializer(serializers.ModelSerializer):
+    identifier = serializers.CharField(source="preprint.identifier_w_vn_nr")
+    url = serializers.URLField(source="get_absolute_url")
+    publication = serializers.SerializerMethodField()
+    acad_field = serializers.StringRelatedField()
+    specialties = serializers.StringRelatedField(many=True)
+    topics = serializers.StringRelatedField(many=True)
+    approaches = serializers.StringRelatedField()
+    submission_date = serializers.CharField(source="submission_date_ymd")
+    is_resubmission_of = serializers.SerializerMethodField()
+    submitted_to = serializers.StringRelatedField()
+    thread_sequence_order = serializers.IntegerField()
+    preprint = PreprintPublicSerializer()
+    reports = serializers.SerializerMethodField()
+    comments = serializers.SerializerMethodField()
+    events = SubmissionEventPublicSerializer(many=True, read_only=True)
+
+    class Meta:
+        model = Submission
+        fields = [
+            "title",
+            "author_list",
+            "abstract",
+            "identifier",
+            "url",
+            "publication",
+            "acad_field",
+            "specialties",
+            "topics",
+            "approaches",
+            "status",
+            "is_current",
+            "submission_date",
+            "original_submission_date",
+            "submitted_to",
+            "proceedings",
+            "is_resubmission_of",
+            "thread_hash",
+            "thread_sequence_order",
+            "code_repository_url",
+            "data_repository_url",
+            "preprint",
+            "reports",
+            "comments",
+            "events",
+        ]
+
+    def get_publication(self, obj):
+        if hasattr(obj, "publication") and obj.publication.is_published:
+            return obj.publication.get_absolute_url()
+        return None
+
+    def get_is_resubmission_of(self, obj):
+        if obj.is_resubmission_of:
+            return obj.is_resubmission_of.get_absolute_url()
+        return None
+
+    def get_reports(self, obj):
+        reports = obj.reports.accepted()
+        return ReportPublicSerializer(reports, many=True, read_only=True).data
+
+    def get_comments(self, obj):
+        comments = obj.comments.vetted()
+        return CommentPublicSerializer(comments, many=True, read_only=True).data
 
 
 class SubmissionPublicSearchSerializer(serializers.ModelSerializer):
diff --git a/scipost_django/submissions/api/serializers/submission_event.py b/scipost_django/submissions/api/serializers/submission_event.py
new file mode 100644
index 000000000..d6523582b
--- /dev/null
+++ b/scipost_django/submissions/api/serializers/submission_event.py
@@ -0,0 +1,19 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from rest_framework import serializers
+
+from ...models import SubmissionEvent
+
+
+class SubmissionEventPublicSerializer(serializers.ModelSerializer):
+    event = serializers.CharField(source="get_event_display")
+
+    class Meta:
+        model = SubmissionEvent
+        fields = [
+            "created",
+            "event",
+            "submission",
+        ]
diff --git a/scipost_django/submissions/api/viewsets/__init__.py b/scipost_django/submissions/api/viewsets/__init__.py
index 220db5cda..fa2550d10 100644
--- a/scipost_django/submissions/api/viewsets/__init__.py
+++ b/scipost_django/submissions/api/viewsets/__init__.py
@@ -2,4 +2,7 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
-from .submission import SubmissionPublicSearchAPIViewSet
+from .submission import (
+    SubmissionPublicAPIViewSet,
+    SubmissionPublicSearchAPIViewSet,
+)
diff --git a/scipost_django/submissions/api/viewsets/submission.py b/scipost_django/submissions/api/viewsets/submission.py
index 52cb196fc..915a8c5e8 100644
--- a/scipost_django/submissions/api/viewsets/submission.py
+++ b/scipost_django/submissions/api/viewsets/submission.py
@@ -8,8 +8,34 @@ from rest_framework.permissions import AllowAny
 from api.viewsets.mixins import FilteringOptionsActionMixin
 
 from submissions.models import Submission
-from submissions.api.filtersets import SubmissionPublicSearchAPIFilterSet
-from submissions.api.serializers import SubmissionPublicSearchSerializer
+from submissions.api.filtersets import (
+    SubmissionPublicAPIFilterSet,
+    SubmissionPublicSearchAPIFilterSet,
+)
+from submissions.api.serializers import (
+    SubmissionPublicSerializer,
+    SubmissionPublicSearchSerializer,
+)
+
+
+class SubmissionPublicAPIViewSet(
+    FilteringOptionsActionMixin, viewsets.ReadOnlyModelViewSet
+):
+    queryset = Submission.objects.public()
+    permission_classes = [
+        AllowAny,
+    ]
+    serializer_class = SubmissionPublicSerializer
+    search_fields = ["title", "author_list", "abstract"]
+    ordering_fields = [
+        "submission_date",
+    ]
+    filterset_class = SubmissionPublicAPIFilterSet
+    default_filtering_fields = [
+        "title__icontains",
+        "author_list__icontains",
+        "abstract__icontains",
+    ]
 
 
 class SubmissionPublicSearchAPIViewSet(
-- 
GitLab