diff --git a/scipost_django/comments/forms.py b/scipost_django/comments/forms.py index 699a3cc7e3852b796c4b53518b4561f10ecba3e3..62545b84dbd70ca1a8d56734ac32b4d98328a703 100644 --- a/scipost_django/comments/forms.py +++ b/scipost_django/comments/forms.py @@ -3,6 +3,11 @@ __license__ = "AGPL v3" from django import forms +from django.db.models import Q + +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Layout, Div +from crispy_bootstrap5.bootstrap5 import FloatingField from .constants import COMMENT_ACTION_CHOICES, COMMENT_ACTION_REFUSE, \ COMMENT_REFUSAL_CHOICES, COMMENT_REFUSAL_EMPTY @@ -48,7 +53,7 @@ class VetCommentForm(forms.Form): return data -class CommentSearchForm(forms.Form): +class CommentTextSearchForm(forms.Form): """Search for Comment""" text = forms.CharField(max_length=1000, required=False, label="Text") @@ -57,3 +62,43 @@ class CommentSearchForm(forms.Form): return Comment.objects.vetted().filter( comment_text__icontains=self.cleaned_data['text'], ).order_by('-date_submitted') + + +class CommentSearchForm(forms.Form): + object_title = forms.CharField( + max_length=100, + required=False + ) + + def __init__(self, *args, **kwargs): + self.acad_field_slug = kwargs.pop('acad_field_slug') + self.specialty_slug = kwargs.pop('specialty_slug') + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + Div( + Div(FloatingField('object_title'), css_class='col-lg-6'), + ), + ) + + def search_results(self): + comments = Comment.objects.vetted() + if self.acad_field_slug != 'all': + comments = comments.filter( + Q(submissions__acad_field__slug=self.acad_field_slug) | + Q(reports__submission__acad_field__slug=self.acad_field_slug) | + Q(commentaries__acad_field__slug=self.acad_field_slug) + ) + if self.specialty_slug: + comments = comments.filter( + Q(submissions__specialties__slug=self.specialty_slug) | + Q(reports__submission__specialties__slug=self.specialty_slug) | + Q(commentaries__specialties__slug=self.specialty_slug) + ) + if self.cleaned_data.get('object_title'): + comments = comments.filter( + Q(submissions__title__icontains=self.cleaned_data.get('object_title')) | + Q(reports__submission__title__icontains=self.cleaned_data.get('object_title')) | + Q(commentaries__title__icontains=self.cleaned_data.get('object_title')) + ) + return comments.distinct() diff --git a/scipost_django/comments/migrations/0007_alter_comment_options.py b/scipost_django/comments/migrations/0007_alter_comment_options.py new file mode 100644 index 0000000000000000000000000000000000000000..fbf275a7eddb63f38986a2a7084081fb69a269c5 --- /dev/null +++ b/scipost_django/comments/migrations/0007_alter_comment_options.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.5 on 2021-10-30 15:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('comments', '0006_alter_comment_needs_doi'), + ] + + operations = [ + migrations.AlterModelOptions( + name='comment', + options={'ordering': ['-date_submitted'], 'permissions': (('can_vet_comments', 'Can vet submitted Comments'),)}, + ), + ] diff --git a/scipost_django/comments/models.py b/scipost_django/comments/models.py index f2bb6e6045dcc27b70b53f89e16396731437affd..3b58fe0da7c17ae17c7bfba57d4e27a29370e1af 100644 --- a/scipost_django/comments/models.py +++ b/scipost_django/comments/models.py @@ -81,6 +81,7 @@ class Comment(TimeStampedModel): objects = CommentQuerySet.as_manager() class Meta: + ordering = ['-date_submitted'] permissions = ( ('can_vet_comments', 'Can vet submitted Comments'), ) diff --git a/scipost_django/comments/views.py b/scipost_django/comments/views.py index f2b7c9da20a63f896dd653baf88516788bc0dacd..5c451d589b620a2bf93cb37f13b05ea2ddec3704 100644 --- a/scipost_django/comments/views.py +++ b/scipost_django/comments/views.py @@ -16,7 +16,7 @@ import strings from .constants import EXTENTIONS_IMAGES, EXTENTIONS_PDF from .models import Comment -from .forms import CommentForm, VetCommentForm, CommentSearchForm +from .forms import CommentForm, VetCommentForm, CommentTextSearchForm from .utils import validate_file_extention from commentaries.models import Commentary @@ -28,7 +28,7 @@ from theses.models import ThesisLink class CommentListView(PaginationMixin, ListView): model = Comment - form = CommentSearchForm + form = CommentTextSearchForm paginate_by = 10 context_object_name = 'comment_list' diff --git a/scipost_django/scipost/templates/scipost/portal/_hx_comments.html b/scipost_django/scipost/templates/scipost/portal/_hx_comments.html new file mode 100644 index 0000000000000000000000000000000000000000..d5936697085ecad210287a1381ce696a641f95ae --- /dev/null +++ b/scipost_django/scipost/templates/scipost/portal/_hx_comments.html @@ -0,0 +1,32 @@ +{% load crispy_forms_tags %} + +<div class="d-flex justify-content-between"> + <button class="btn btn-outline-primary" data-bs-toggle="collapse" data-bs-target="#commentsSearch" aria-expanded="false" aria-controls="commentsSearch"> + {% include 'bi/search.html' %} Simple search / filter + </button> + <a class="btn btn-outline-primary ms-2" href="{% url 'scipost:search' %}"> + {% include 'bi/binoculars-fill.html' %}... or use our advanced search API {% include 'bi/arrow-right.html' %} + </a> +</div> +<div class="collapse" id="commentsSearch"> + <div class="card card-body"> + <form + hx-post="{% url 'scipost:portal_hx_comments_page' %}?page=1" + hx-trigger="load, keyup delay: 500ms, change" + hx-target="#comments-search-results" + hx-indicator="#indicator-comments-search" + > + <div id="comments-search-form">{% crispy comments_search_form %}</div> + </form> + </div> + <div id="indicator-comments-search" class="htmx-indicator p-2"> + <button class="btn btn-warning" type="button" disabled> + <strong>Loading...</strong> + <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> + </button> + </div> +</div> + +<h2 class="highlight mb-0">Comments on objects in {{ session_acad_field }}: {% if session_specialty %}{{ session_specialty }}{% else %}(all specialties){% endif %}</h2> + +<ul id="comments-search-results" class="list-unstyled pool-list mt-2"></ul> diff --git a/scipost_django/scipost/templates/scipost/portal/_hx_comments_page.html b/scipost_django/scipost/templates/scipost/portal/_hx_comments_page.html new file mode 100644 index 0000000000000000000000000000000000000000..e6e45ee8ae53e49eb42fa6a4f032760ec03e5d38 --- /dev/null +++ b/scipost_django/scipost/templates/scipost/portal/_hx_comments_page.html @@ -0,0 +1,28 @@ +{% for comment in page_obj %} + <li class="list-group-item py-2"> + {% include 'comments/_comment_card_content.html' with comment=comment %} + </li> +{% empty %} + <li class="list-group-item py-2"> + No comments yet + </li> +{% endfor %} +{% if page_obj.has_next %} + <li id="next-comments-{{ page_obj.number }}"> + <button class="btn btn-primary m-2" type="button" + hx-post="{% url 'scipost:portal_hx_comments_page' %}?page={{ page_obj.next_page_number }}" + hx-include="#comments-search-form" + hx-target="#next-comments-{{ page_obj.number }}" + hx-swap="outerHTML" + hx-indicator="#indicator-comments-page-{{ page_obj.number }}" + > + Load page {{ page_obj.next_page_number }} (out of {{ page_obj.paginator.num_pages }}) + </button> + <span id="indicator-comments-page-{{ page_obj.number }}" class="htmx-indicator p-2"> + <button class="btn btn-warning" type="button" disabled> + <strong>Loading page {{ page_obj.next_page_number }} out of {{ page_obj.paginator.num_pages }}</strong> + <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> + </button> + </span> + </li> +{% endif %} diff --git a/scipost_django/scipost/templates/scipost/portal/_hx_reports.html b/scipost_django/scipost/templates/scipost/portal/_hx_reports.html new file mode 100644 index 0000000000000000000000000000000000000000..19710b825cfc0be6515520306bc1e39375ebbe82 --- /dev/null +++ b/scipost_django/scipost/templates/scipost/portal/_hx_reports.html @@ -0,0 +1,32 @@ +{% load crispy_forms_tags %} + +<div class="d-flex justify-content-between"> + <button class="btn btn-outline-primary" data-bs-toggle="collapse" data-bs-target="#reportsSearch" aria-expanded="false" aria-controls="reportsSearch"> + {% include 'bi/search.html' %} Simple search / filter + </button> + <a class="btn btn-outline-primary ms-2" href="{% url 'scipost:search' %}"> + {% include 'bi/binoculars-fill.html' %}... or use our advanced search API {% include 'bi/arrow-right.html' %} + </a> +</div> +<div class="collapse" id="reportsSearch"> + <div class="card card-body"> + <form + hx-post="{% url 'scipost:portal_hx_reports_page' %}?page=1" + hx-trigger="load, keyup delay: 500ms, change" + hx-target="#reports-search-results" + hx-indicator="#indicator-reports-search" + > + <div id="reports-search-form">{% crispy reports_search_form %}</div> + </form> + </div> + <div id="indicator-reports-search" class="htmx-indicator p-2"> + <button class="btn btn-warning" type="button" disabled> + <strong>Loading...</strong> + <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> + </button> + </div> +</div> + +<h2 class="highlight mb-0">Reports on Submissions in {{ session_acad_field }}: {% if session_specialty %}{{ session_specialty }}{% else %}(all specialties){% endif %}</h2> + +<ul id="reports-search-results" class="list-unstyled pool-list mt-2"></ul> diff --git a/scipost_django/scipost/templates/scipost/portal/_hx_reports_page.html b/scipost_django/scipost/templates/scipost/portal/_hx_reports_page.html new file mode 100644 index 0000000000000000000000000000000000000000..e2ce46301b54f2a034c5234d86b259cd36e2e70e --- /dev/null +++ b/scipost_django/scipost/templates/scipost/portal/_hx_reports_page.html @@ -0,0 +1,28 @@ +{% for report in page_obj %} + <li class="list-group-item py-2"> + {% include 'submissions/_report_li_content.html' with report=report %} + </li> +{% empty %} + <li class="list-group-item py-2"> + No reports yet + </li> +{% endfor %} +{% if page_obj.has_next %} + <li id="next-reports-{{ page_obj.number }}"> + <button class="btn btn-primary m-2" type="button" + hx-post="{% url 'scipost:portal_hx_reports_page' %}?page={{ page_obj.next_page_number }}" + hx-include="#reports-search-form" + hx-target="#next-reports-{{ page_obj.number }}" + hx-swap="outerHTML" + hx-indicator="#indicator-reports-page-{{ page_obj.number }}" + > + Load page {{ page_obj.next_page_number }} (out of {{ page_obj.paginator.num_pages }}) + </button> + <span id="indicator-reports-page-{{ page_obj.number }}" class="htmx-indicator p-2"> + <button class="btn btn-warning" type="button" disabled> + <strong>Loading page {{ page_obj.next_page_number }} out of {{ page_obj.paginator.num_pages }}</strong> + <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> + </button> + </span> + </li> +{% endif %} diff --git a/scipost_django/scipost/templates/scipost/portal/_hx_submissions_page.html b/scipost_django/scipost/templates/scipost/portal/_hx_submissions_page.html index 862fb37b7b67b83a29a3ddd86889baca8fbe03e8..0beb2f9b24d9391b5e1b57a51cf13ba3a8de54da 100644 --- a/scipost_django/scipost/templates/scipost/portal/_hx_submissions_page.html +++ b/scipost_django/scipost/templates/scipost/portal/_hx_submissions_page.html @@ -2,6 +2,9 @@ <li class="list-group-item py-2"> {% include 'submissions/_submission_card_content_homepage.html' with submission=submission %} </li> + <li class="list-group-item py-2"> + {% include 'submissions/_submission_li.html' with submission=submission %} + </li> {% empty %} <li class="list-group-item py-2"> No submissions yet diff --git a/scipost_django/scipost/templates/scipost/portal/portal.html b/scipost_django/scipost/templates/scipost/portal/portal.html index 45f5e3340de9e019cd2e53c6c716865661932724..1bb8942fe314bcbc8da87c8397007cd2a920fb88 100644 --- a/scipost_django/scipost/templates/scipost/portal/portal.html +++ b/scipost_django/scipost/templates/scipost/portal/portal.html @@ -91,31 +91,37 @@ role="tabpanel" aria-labelledby="publications-tab"> <div hx-get="{% url 'scipost:portal_hx_publications' %}" hx-trigger="load, click from:#publications-tab, session-acad-field-set[document.getElementById('publications').classList.contains('active')] from:body, session-specialty-set[document.getElementById('publications').classList.contains('active')] from:body" - > - </div> + > </div> + </div> - <div class="tab-pane fade" id="submissions" - role="tabpanel" aria-labelledby="submissions-tab"> - <div hx-get="{% url 'scipost:portal_hx_submissions' %}" - hx-trigger="click from:#submissions-tab, session-acad-field-set[document.getElementById('submissions').classList.contains('active')] from:body, session-specialty-set[document.getElementById('submissions').classList.contains('active')] from:body" - > - </div> + <div class="tab-pane fade" id="submissions" + role="tabpanel" aria-labelledby="submissions-tab"> + <div hx-get="{% url 'scipost:portal_hx_submissions' %}" + hx-trigger="click from:#submissions-tab, session-acad-field-set[document.getElementById('submissions').classList.contains('active')] from:body, session-specialty-set[document.getElementById('submissions').classList.contains('active')] from:body" + > </div> + </div> - <div class="tab-pane fade" id="reports-needed" - role="tabpanel" aria-labelledby="reports-needed-tab"> + <div class="tab-pane fade" id="reports-needed" + role="tabpanel" aria-labelledby="reports-needed-tab"> Reports needed! </div> <div class="tab-pane fade" id="reports" role="tabpanel" aria-labelledby="reports-tab"> - Reports + <div hx-get="{% url 'scipost:portal_hx_reports' %}" + hx-trigger="click from:#reports-tab, session-acad-field-set[document.getElementById('reports').classList.contains('active')] from:body, session-specialty-set[document.getElementById('reports').classList.contains('active')] from:body" + > + </div> </div> <div class="tab-pane fade" id="comments" role="tabpanel" aria-labelledby="comments-tab"> - Comments + <div hx-get="{% url 'scipost:portal_hx_comments' %}" + hx-trigger="click from:#comments-tab, session-acad-field-set[document.getElementById('comments').classList.contains('active')] from:body, session-specialty-set[document.getElementById('comments').classList.contains('active')] from:body" + > + </div> </div> </div> diff --git a/scipost_django/scipost/urls.py b/scipost_django/scipost/urls.py index 0d4c0b2261e155155b5f8100483fb3c3037293f5..d8772f361a73f984756b9635be0964f9287e7f28 100644 --- a/scipost_django/scipost/urls.py +++ b/scipost_django/scipost/urls.py @@ -113,6 +113,26 @@ urlpatterns = [ views.portal_hx_submissions_page, name='portal_hx_submissions_page' ), + path( + 'portal/_hx_reports', + views.portal_hx_reports, + name='portal_hx_reports' + ), + path( + 'portal/_hx_reports_page', + views.portal_hx_reports_page, + name='portal_hx_reports_page' + ), + path( + 'portal/_hx_comments', + views.portal_hx_comments, + name='portal_hx_comments' + ), + path( + 'portal/_hx_comments_page', + views.portal_hx_comments_page, + name='portal_hx_comments_page' + ), path( '_hx_news', views._hx_news, diff --git a/scipost_django/scipost/views.py b/scipost_django/scipost/views.py index d323e0c5926ff1660e97834d173b8c124a6f53d9..42ae7892f430485eaf87bda91fbaf5bdb01a8954 100644 --- a/scipost_django/scipost/views.py +++ b/scipost_django/scipost/views.py @@ -56,6 +56,7 @@ from .utils import EMAIL_FOOTER, SCIPOST_SUMMARY_FOOTER, SCIPOST_SUMMARY_FOOTER_ from colleges.permissions import fellowship_or_admin_required from commentaries.models import Commentary from comments.models import Comment +from comments.forms import CommentSearchForm from invitations.constants import STATUS_REGISTERED from invitations.models import RegistrationInvitation from journals.models import Journal, Publication, PublicationAuthorsTable @@ -67,7 +68,7 @@ from organizations.models import Organization, Contact from organizations.forms import UpdateContactDataForm from profiles.models import Profile from submissions.models import Submission, RefereeInvitation, Report, EICRecommendation -from submissions.forms import SubmissionSearchForm +from submissions.forms import SubmissionSearchForm, ReportSearchForm from theses.models import ThesisLink @@ -266,6 +267,82 @@ def portal_hx_submissions_page(request): return render(request, 'scipost/portal/_hx_submissions_page.html', context) +def portal_hx_reports(request): + form = ReportSearchForm( + acad_field_slug=request.session.get('session_acad_field_slug', None), + specialty_slug=request.session.get('session_specialty_slug', None) + ) + context = { + 'reports_search_form': form + } + return render(request, 'scipost/portal/_hx_reports.html', context) + + +def portal_hx_reports_page(request): + session_acad_field_slug = request.session.get('session_acad_field_slug', None) + session_specialty_slug = request.session.get('session_specialty_slug', None) + form = ReportSearchForm( + request.POST or None, + acad_field_slug=session_acad_field_slug, + specialty_slug=session_specialty_slug, + ) + if form.is_valid(): + reports = form.search_results() + else: + reports = Report.objects.accepted() + if session_acad_field_slug and session_acad_field_slug != 'all': + reports = reports.filter(submission__acad_field__slug=session_acad_field_slug) + if session_specialty_slug: + reports = reports.filter(submission__specialties__slug=session_specialty_slug) + paginator = Paginator(reports, 10) + page_nr = request.GET.get('page') + page_obj = paginator.get_page(page_nr) + context = { 'page_obj': page_obj } + return render(request, 'scipost/portal/_hx_reports_page.html', context) + + +def portal_hx_comments(request): + form = CommentSearchForm( + acad_field_slug=request.session.get('session_acad_field_slug', None), + specialty_slug=request.session.get('session_specialty_slug', None) + ) + context = { + 'comments_search_form': form + } + return render(request, 'scipost/portal/_hx_comments.html', context) + + +def portal_hx_comments_page(request): + session_acad_field_slug = request.session.get('session_acad_field_slug', None) + session_specialty_slug = request.session.get('session_specialty_slug', None) + form = CommentSearchForm( + request.POST or None, + acad_field_slug=session_acad_field_slug, + specialty_slug=session_specialty_slug, + ) + if form.is_valid(): + comments = form.search_results() + else: + comments = Comment.objects.vetted() + if session_acad_field_slug and session_acad_field_slug != 'all': + comments = comments.filter( + Q(submissions__acad_field__slug=session_acad_field_slug) | + Q(reports__submission__acad_field__slug=session_acad_field_slug) | + Q(commentaries__acad_field__slug=session_acad_field_slug) + ) + if session_specialty_slug: + comments = comments.filter( + Q(submissions__specialties__slug=session_specialty_slug) | + Q(reports__submission__specialties__slug=session_specialty_slug) | + Q(commentaries__specialties__slug=session_specialty_slug) + ) + paginator = Paginator(comments.distinct(), 10) + page_nr = request.GET.get('page') + page_obj = paginator.get_page(page_nr) + context = { 'page_obj': page_obj } + return render(request, 'scipost/portal/_hx_comments_page.html', context) + + def _hx_news(request): if NewsItem.objects.homepage().exists(): latest_newsitem_id = NewsItem.objects.homepage().order_by('-date').first().id diff --git a/scipost_django/submissions/forms.py b/scipost_django/submissions/forms.py index 5962c0bc90ee865aa2c391c416be12fbb47741c8..dd5c6e69a9d0a0138d26b6f561de17e83ff8099d 100644 --- a/scipost_django/submissions/forms.py +++ b/scipost_django/submissions/forms.py @@ -339,6 +339,35 @@ class SubmissionPoolSearchForm(forms.Form): return submissions +class ReportSearchForm(forms.Form): + submission_title = forms.CharField( + max_length=100, + required=False + ) + + def __init__(self, *args, **kwargs): + self.acad_field_slug = kwargs.pop('acad_field_slug') + self.specialty_slug = kwargs.pop('specialty_slug') + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + Div( + Div(FloatingField('submission_title'), css_class='col-lg-6'), + ), + ) + + def search_results(self): + reports = Report.objects.accepted() + if self.acad_field_slug != 'all': + reports = reports.filter(submission__acad_field__slug=self.acad_field_slug) + if self.specialty_slug: + reports = reports.filter(submission__specialties__slug=self.specialty_slug) + if self.cleaned_data.get('submission_title'): + reports = reports.filter( + submission__title__icontains=self.cleaned_data.get('submission_title')) + return reports + + # Marked for deprecation class SubmissionOldSearchForm(forms.Form): """Filter a Submission queryset using basic search fields.""" @@ -384,6 +413,8 @@ class SubmissionPoolFilterForm(forms.Form): return '' + + ###################################################################### # # SubmissionForm prefill facilities. One class per integrated server. diff --git a/scipost_django/submissions/templates/submissions/_report_li_content.html b/scipost_django/submissions/templates/submissions/_report_li_content.html new file mode 100644 index 0000000000000000000000000000000000000000..b257e06ed039bd71c14af4303e67154b9f06b6b1 --- /dev/null +++ b/scipost_django/submissions/templates/submissions/_report_li_content.html @@ -0,0 +1,6 @@ +{% include 'submissions/_submission_li.html' with submission=report.submission %} + +<div class="card-body {% block cardblock_class_block %}{% endblock %}"> + <h3><a href="{{ report.get_absolute_url }}">Report {{ report.report_nr }}</a> by {% if report.anonymous %}<em>anonymous</em>{% else %}<a href="{{ report.author.get_absolute_url }}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a>{% endif %}</h3> + <h4>Received: {{ report.date_submitted|date:'Y-n-j' }}</h4> +</div> diff --git a/scipost_django/submissions/templates/submissions/_report_summary.html b/scipost_django/submissions/templates/submissions/_report_summary.html index 1db60b76f8432420a1018639353ad9bd9342caca..9cd93e526d071c832e772fc5b96515eb5f84ef97 100644 --- a/scipost_django/submissions/templates/submissions/_report_summary.html +++ b/scipost_django/submissions/templates/submissions/_report_summary.html @@ -1,5 +1,5 @@ <div class="card-body {% block cardblock_class_block %}{% endblock %}"> - <h3>{{report.get_status_display}} Report {{report.report_nr}} by {% if report.anonymous %}<em>anonymous</em>{% else %}<a href="{{report.author.get_absolute_url}}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a>{% endif %}</h3> + <h3>{{ report.get_status_display }} Report {{ report.report_nr }} by {% if report.anonymous %}<em>anonymous</em>{% else %}<a href="{{ report.author.get_absolute_url }}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a>{% endif %}</h3> <h4>Received: {{ report.date_submitted|date:'Y-n-j' }}</h4> - On Submission: <a href="{{report.submission.get_absolute_url}}">{{report.submission}}</a> + On Submission: <a href="{{ report.submission.get_absolute_url }}">{{ report.submission }}</a> </div>