From 3b80b0895ab49caf6aa8740b408c4e57ffc38324 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Caux?= <git@jscaux.org>
Date: Fri, 28 Jan 2022 18:07:49 +0100
Subject: [PATCH] Install basic HTMX-driven searchable listing of nominations

---
 scipost_django/colleges/forms.py              | 51 +++++++++++++++++--
 .../templates/colleges/_hx_nomination_li.html |  1 +
 .../templates/colleges/_hx_nominations.html   | 22 ++++++++
 .../templates/colleges/nominations.html       | 42 +++++++++++++++
 scipost_django/colleges/urls.py               | 12 +++++
 scipost_django/colleges/views.py              | 43 ++++++++++++++--
 6 files changed, 164 insertions(+), 7 deletions(-)
 create mode 100644 scipost_django/colleges/templates/colleges/_hx_nomination_li.html
 create mode 100644 scipost_django/colleges/templates/colleges/_hx_nominations.html
 create mode 100644 scipost_django/colleges/templates/colleges/nominations.html

diff --git a/scipost_django/colleges/forms.py b/scipost_django/colleges/forms.py
index 06f13cf2b..6c04ab986 100644
--- a/scipost_django/colleges/forms.py
+++ b/scipost_django/colleges/forms.py
@@ -8,7 +8,7 @@ from django import forms
 from django.db.models import Q
 
 from crispy_forms.helper import FormHelper
-from crispy_forms.layout import Layout, Field
+from crispy_forms.layout import Layout, Div, Field
 from crispy_bootstrap5.bootstrap5 import FloatingField
 from dal import autocomplete
 
@@ -18,9 +18,15 @@ from submissions.models import Submission
 from scipost.forms import RequestFormMixin
 from scipost.models import Contributor
 
-from .models import Fellowship, PotentialFellowship, PotentialFellowshipEvent
-from .constants import POTENTIAL_FELLOWSHIP_IDENTIFIED, POTENTIAL_FELLOWSHIP_NOMINATED,\
+from .models import (
+    College, Fellowship,
+    PotentialFellowship, PotentialFellowshipEvent,
+    FellowshipNomination,
+)
+from .constants import (
+    POTENTIAL_FELLOWSHIP_IDENTIFIED, POTENTIAL_FELLOWSHIP_NOMINATED,
     POTENTIAL_FELLOWSHIP_EVENT_DEFINED, POTENTIAL_FELLOWSHIP_EVENT_NOMINATED
+)
 
 
 class FellowshipSelectForm(forms.Form):
@@ -270,3 +276,42 @@ class PotentialFellowshipEventForm(forms.ModelForm):
         self.fields['comments'].widget.attrs.update({
             'placeholder': 'NOTA BENE: careful, will be visible to all who have voting rights'
             })
+
+
+###############
+# Nominations #
+###############
+
+class FellowshipNominationSearchForm(forms.Form):
+    """Filter a FellowshipNomination queryset using basic search fields."""
+
+    college = forms.ModelChoiceField(
+        queryset=College.objects.all(),
+        required=False
+    )
+    profile = forms.ModelChoiceField(
+        queryset=Profile.objects.all(),
+        widget=autocomplete.ModelSelect2(url='/profiles/profile-autocomplete'),
+        required=False
+    )
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.helper = FormHelper()
+        self.helper.layout = Layout(
+            Div(
+                Div(FloatingField('college'), css_class='col-lg-6'),
+                Div(FloatingField('profile'), css_class='col-lg-6'),
+                css_class='row'
+            )
+        )
+
+    def search_results(self):
+        if self.cleaned_data.get('profile'):
+            nominations = FellowshipNomination.objects.filter(
+                profile=self.cleaned_data.get('profile'))
+        else:
+            nominations = FellowshipNomination.objects.all()
+        if self.cleaned_data.get('college'):
+            nominations = nominations.filter(college=self.cleaned_data.get('college'))
+        return nominations
diff --git a/scipost_django/colleges/templates/colleges/_hx_nomination_li.html b/scipost_django/colleges/templates/colleges/_hx_nomination_li.html
new file mode 100644
index 000000000..a5aaf6406
--- /dev/null
+++ b/scipost_django/colleges/templates/colleges/_hx_nomination_li.html
@@ -0,0 +1 @@
+{{ nomination }}
diff --git a/scipost_django/colleges/templates/colleges/_hx_nominations.html b/scipost_django/colleges/templates/colleges/_hx_nominations.html
new file mode 100644
index 000000000..1265eec30
--- /dev/null
+++ b/scipost_django/colleges/templates/colleges/_hx_nominations.html
@@ -0,0 +1,22 @@
+{% for nomination in page_obj %}
+  <li class="p-2 mb-2" id="nomination_{{ nomination.id }}">
+    {% include 'colleges/_hx_nomination_li.html' with nomination=nomination %}
+  </li>
+{% empty %}
+  <li>No Nomination could be found</li>
+{% endfor %}
+{% if page_obj.has_next %}
+  <li hx-post="{% url 'colleges:_hx_nominations' %}?page={{ page_obj.next_page_number }}"
+      hx-include="#search-form"
+      hx-trigger="revealed"
+      hx-swap="afterend"
+      hx-indicator="#indicator-search-page-{{ page_obj.number }}"
+  >
+    <div id="indicator-search-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>
+    </div>
+  </li>
+{% endif %}
diff --git a/scipost_django/colleges/templates/colleges/nominations.html b/scipost_django/colleges/templates/colleges/nominations.html
new file mode 100644
index 000000000..e3840f52b
--- /dev/null
+++ b/scipost_django/colleges/templates/colleges/nominations.html
@@ -0,0 +1,42 @@
+{% extends 'colleges/base.html' %}
+
+{% load crispy_forms_tags %}
+
+{% block breadcrumb_items %}
+  {{ block.super }}
+  <a href="{% url 'colleges:colleges' %}" class="breadcrumb-item">Colleges</a>
+  <span class="breadcrumb-item">Nominations</span>
+{% endblock %}
+
+{% block meta_description %}{{ block.super }} Nominations{% endblock meta_description %}
+{% block pagetitle %}: Nominations{% endblock pagetitle %}
+
+{% block content %}
+
+  <h1 class="highlight">Fellowship Nominations</h1>
+
+  <div class="card">
+    <div class="card-header">
+      Search / filter
+    </div>
+    <div class="card-body">
+      <form
+	  hx-post="{% url 'colleges:_hx_nominations' %}"
+	  hx-trigger="load, keyup delay:500ms, change"
+	  hx-target="#search-results"
+	  hx-indicator="#indicator-search"
+      >
+	<div id="search-form">{% crispy form %}</div>
+      </form>
+    </div>
+  </div>
+  <div id="indicator-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>
+  <ul id="search-results" class="list-unstyled mt-2"></ul>
+
+
+{% endblock content %}
diff --git a/scipost_django/colleges/urls.py b/scipost_django/colleges/urls.py
index a5bee2f2f..3205efe29 100644
--- a/scipost_django/colleges/urls.py
+++ b/scipost_django/colleges/urls.py
@@ -158,4 +158,16 @@ urlpatterns = [
         views.PotentialFellowshipListView.as_view(),
         name='potential_fellowships'
     ),
+
+    # Nominations
+    path(
+        'nominations',
+        views.nominations,
+        name='nominations'
+    ),
+    path(
+        '_hx_nominations',
+        views._hx_nominations,
+        name='_hx_nominations'
+    ),
 ]
diff --git a/scipost_django/colleges/views.py b/scipost_django/colleges/views.py
index c76603285..64b3b8224 100644
--- a/scipost_django/colleges/views.py
+++ b/scipost_django/colleges/views.py
@@ -9,6 +9,7 @@ from dal import autocomplete
 from django.contrib import messages
 from django.contrib.auth.models import Group
 from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
+from django.core.paginator import Paginator
 from django.urls import reverse, reverse_lazy
 from django.http import Http404
 from django.shortcuts import get_object_or_404, render, redirect
@@ -25,10 +26,14 @@ from .constants import (
     POTENTIAL_FELLOWSHIP_INVITED, POTENTIAL_FELLOWSHIP_ACTIVE_IN_COLLEGE,
     potential_fellowship_statuses_dict,
     POTENTIAL_FELLOWSHIP_EVENT_VOTED_ON, POTENTIAL_FELLOWSHIP_EVENT_EMAILED)
-from .forms import FellowshipDynSelForm, FellowshipForm, FellowshipRemoveSubmissionForm,\
-    FellowshipAddSubmissionForm, SubmissionAddFellowshipForm,\
-    FellowshipRemoveProceedingsForm, FellowshipAddProceedingsForm, \
-    PotentialFellowshipForm, PotentialFellowshipStatusForm, PotentialFellowshipEventForm
+from .forms import (
+    FellowshipDynSelForm, FellowshipForm,
+    FellowshipRemoveSubmissionForm, FellowshipAddSubmissionForm,
+    SubmissionAddFellowshipForm,
+    FellowshipRemoveProceedingsForm, FellowshipAddProceedingsForm,
+    PotentialFellowshipForm, PotentialFellowshipStatusForm, PotentialFellowshipEventForm,
+    FellowshipNominationSearchForm,
+)
 from .models import College, Fellowship, PotentialFellowship, PotentialFellowshipEvent
 
 from scipost.forms import EmailUsersForm, SearchTextForm
@@ -513,3 +518,33 @@ class PotentialFellowshipEventCreateView(PermissionsMixin, CreateView):
         form.instance.noted_by = self.request.user.contributor
         messages.success(self.request, 'Event added successfully')
         return super().form_valid(form)
+
+
+###############
+# Nominations #
+###############
+
+
+@login_required
+@user_passes_test(is_edadmin_or_senior_fellow)
+def nominations(request):
+    """
+    List Nominations.
+    """
+    context = {
+        'form': FellowshipNominationSearchForm()
+    }
+    return render(request, 'colleges/nominations.html', context)
+
+
+def _hx_nominations(request):
+    form = FellowshipNominationSearchForm(request.POST or None)
+    if form.is_valid():
+        nominations = form.search_results()
+    else:
+        nominations = FellowshipNomination.objects.all()
+    paginator = Paginator(nominations, 16)
+    page_nr = request.GET.get('page')
+    page_obj = paginator.get_page(page_nr)
+    context = { 'page_obj': page_obj }
+    return render(request, 'colleges/_hx_nominations.html', context)
-- 
GitLab