From 804eae5aa6dd71c4ac5a4e5120b9cd16c26ed98e Mon Sep 17 00:00:00 2001 From: George Katsikas <giorgakis.katsikas@gmail.com> Date: Mon, 24 Jul 2023 17:53:06 +0300 Subject: [PATCH] add facilities to start a nomination voting round --- scipost_django/colleges/models/nomination.py | 17 ++++++ .../_hx_nomination_eligible_voters.html | 12 ++++ .../_hx_nomination_eligible_voters_table.html | 39 +++++++++++++ .../colleges/_hx_nomination_voter_table.html | 2 +- .../_hx_nominations_no_round_started.html | 16 +++++ .../colleges/_hx_voting_round_results.html | 4 +- .../templates/colleges/nominations.html | 12 +++- scipost_django/colleges/urls.py | 15 +++++ scipost_django/colleges/views.py | 58 +++++++++++++++++++ 9 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 scipost_django/colleges/templates/colleges/_hx_nomination_eligible_voters.html create mode 100644 scipost_django/colleges/templates/colleges/_hx_nomination_eligible_voters_table.html create mode 100644 scipost_django/colleges/templates/colleges/_hx_nominations_no_round_started.html diff --git a/scipost_django/colleges/models/nomination.py b/scipost_django/colleges/models/nomination.py index e42eb87f4..6487d2a84 100644 --- a/scipost_django/colleges/models/nomination.py +++ b/scipost_django/colleges/models/nomination.py @@ -4,6 +4,7 @@ __license__ = "AGPL v3" from django.db import models from django.utils import timezone +from django.utils.functional import cached_property from ..managers import ( FellowshipNominationQuerySet, @@ -11,6 +12,8 @@ from ..managers import ( FellowshipNominationVoteQuerySet, ) +from colleges.models import Fellowship + from scipost.models import get_sentinel_user @@ -105,6 +108,20 @@ class FellowshipNomination(models.Model): return "Latest voting round is ongoing, and not everybody has voted." return "No voting round found." + # FIX: This is wrong semantically... + @property + def get_eligible_voters(self): + specialties_slug_list = [s.slug for s in self.profile.specialties.all()] + + eligible_voters = ( + Fellowship.objects.active() + .senior() + .specialties_overlap(specialties_slug_list) + .distinct() + ) + + return eligible_voters + class FellowshipNominationEvent(models.Model): nomination = models.ForeignKey( diff --git a/scipost_django/colleges/templates/colleges/_hx_nomination_eligible_voters.html b/scipost_django/colleges/templates/colleges/_hx_nomination_eligible_voters.html new file mode 100644 index 000000000..256c3de0e --- /dev/null +++ b/scipost_django/colleges/templates/colleges/_hx_nomination_eligible_voters.html @@ -0,0 +1,12 @@ +<h4>Eligible Voters</h4> + +{% include 'colleges/_hx_nomination_eligible_voters_table.html' with eligible_voters=eligible_voters %} + +{% if eligible_voters|length > 0 %} + <button id="nomination-{{ nomination.id }}-start-round-btn" + hx-get="{% url 'colleges:_hx_nomination_round_start' nomination_id=nomination.id %}" + hx-target="#nomination-{{ nomination.id }}-eligible-voters" + class="nomination-start-round-btn btn btn-{% if eligible_voters|length < 5 %}warning{% else %}primary{% endif %}" + {% if eligible_voters|length < 5 %}hx-confirm="Are you sure you want to start a round with fewer than 5 voters?"{% endif %} + >Start round</button> +{% endif %} diff --git a/scipost_django/colleges/templates/colleges/_hx_nomination_eligible_voters_table.html b/scipost_django/colleges/templates/colleges/_hx_nomination_eligible_voters_table.html new file mode 100644 index 000000000..009a96b26 --- /dev/null +++ b/scipost_django/colleges/templates/colleges/_hx_nomination_eligible_voters_table.html @@ -0,0 +1,39 @@ +{% if eligible_voters %} + <table class="table"> + <thead class="table-light"> + <tr> + <th>Fellow</th> + <th>College</th> + <th>Specialties</th> + <th>Type</th> + </tr> + </thead> + + <tbody> + + {% for voter in eligible_voters %} + <tr> + <td>{{ voter.contributor }}</td> + <td>{{ voter.college.name }}</td> + <td> + + {% for specialty in voter.contributor.profile.specialties.all %} + <div class="single d-inline + {% if specialty in nominee_specialties %}text-success{% endif %} + " data-specialty="{{ specialty.slug }}" data-bs-placement="bottom" title="{{ specialty }}"> + {{ specialty.code }} + </div> + {% endfor %} + + + </td> + <td>{{ voter.get_status_display }}</td> + </tr> + {% endfor %} + + + </tbody> + </table> +{% else %} + <p class="text-danger">No eligible voters found.</p> +{% endif %} diff --git a/scipost_django/colleges/templates/colleges/_hx_nomination_voter_table.html b/scipost_django/colleges/templates/colleges/_hx_nomination_voter_table.html index ecc14a497..27f9622f3 100644 --- a/scipost_django/colleges/templates/colleges/_hx_nomination_voter_table.html +++ b/scipost_django/colleges/templates/colleges/_hx_nomination_voter_table.html @@ -1,5 +1,5 @@ {% if voters %} - <table class="table"> + <table class="table mb-0"> <thead class="table-light"> <tr> <th>Fellow</th> diff --git a/scipost_django/colleges/templates/colleges/_hx_nominations_no_round_started.html b/scipost_django/colleges/templates/colleges/_hx_nominations_no_round_started.html new file mode 100644 index 000000000..cf3332cdf --- /dev/null +++ b/scipost_django/colleges/templates/colleges/_hx_nominations_no_round_started.html @@ -0,0 +1,16 @@ +{% for nomination in nominations_no_round_started %} + <details id="nomination-{{ nomination.id }}-start-round" + class="border border-2 my-2"> + <summary class="p-2 bg-light">{{ nomination }}</summary> + + <div class="p-2 mt-2"> + <div id="nomination-{{ nomination.id }}-eligible-voters" + hx-get="{% url 'colleges:_hx_nomination_eligible_voters' nomination_id=nomination.id %}" + hx-trigger="toggle from:#nomination-{{ nomination.id }}-start-round"></div> + + </div> + + </details> +{% empty %} + <p>All nominations have had their voting round(s) started.</p> +{% endfor %} diff --git a/scipost_django/colleges/templates/colleges/_hx_voting_round_results.html b/scipost_django/colleges/templates/colleges/_hx_voting_round_results.html index 646a4a9f4..a6d5f3d3e 100644 --- a/scipost_django/colleges/templates/colleges/_hx_voting_round_results.html +++ b/scipost_django/colleges/templates/colleges/_hx_voting_round_results.html @@ -10,7 +10,7 @@ {% endif %} <div class="row mb-0"> - <div class="col"> + <div class="col-12 col-md mt-3"> <h3>Decision</h3> {% if voting_round.decision.outcome == 'elected' %} @@ -37,6 +37,6 @@ {% endif %} </div> - <div class="col">{% include "colleges/_voting_results_box.html" with voting_round=voting_round %}</div> + <div class="col-12 col-md mt-3">{% include "colleges/_voting_results_box.html" with voting_round=voting_round %}</div> </div> </div> diff --git a/scipost_django/colleges/templates/colleges/nominations.html b/scipost_django/colleges/templates/colleges/nominations.html index bfabf3fb5..d8eb94154 100644 --- a/scipost_django/colleges/templates/colleges/nominations.html +++ b/scipost_django/colleges/templates/colleges/nominations.html @@ -106,13 +106,20 @@ <details id="ensure-specialties-details" class="border border-danger border-2 mt-4"> <summary class="bg-danger bg-opacity-10 p-2 d-flex list-triangle"> - <div class="fs-5">Ensure specialties</div> + <div class="fs-5">Start voting</div> </summary> <div class="p-2"> + <h3>Add specialties</h3> <div id="nominations_needing_specialties" hx-get="{% url 'colleges:_hx_nominations_needing_specialties' %}" hx-trigger="toggle from:#ensure-specialties-details"></div> + + <h3 class="mt-4 ">Start round</h3> + <div id="nominations_no_round_started" + hx-get="{% url 'colleges:_hx_nominations_no_round_started' %}" + hx-trigger="toggle from:#ensure-specialties-details, click from:body target:.nomination-start-round-btn"> + </div> </div> </details> {% endif %} @@ -133,7 +140,8 @@ <div class="p-2"> <div id="voting_tablist" hx-get="{% url 'colleges:_hx_voting_rounds' %}?tab= {% if 'edadmin' in user_roles %}ongoing{% else %}ongoing-vote_required{% endif %} - " hx-trigger="toggle from:#voting-details" hx-target="this" hx-swap="innerHTML"></div> + " hx-trigger="toggle from:#voting-details, click from:body target:.nomination-start-round-btn" hx-target="this" hx-swap="innerHTML"> + </div> </div> </details> diff --git a/scipost_django/colleges/urls.py b/scipost_django/colleges/urls.py index 11e17f1a8..01027c691 100644 --- a/scipost_django/colleges/urls.py +++ b/scipost_django/colleges/urls.py @@ -189,6 +189,21 @@ urlpatterns = [ views._hx_nominations_needing_specialties, name="_hx_nominations_needing_specialties", ), + path( + "_hx_nominations_no_round_started", + views._hx_nominations_no_round_started, + name="_hx_nominations_no_round_started", + ), + path( + "_hx_nomination_eligible_voters/<int:nomination_id>", + views._hx_nomination_eligible_voters, + name="_hx_nomination_eligible_voters", + ), + path( + "_hx_nomination_round_start/<int:nomination_id>", + views._hx_nomination_round_start, + name="_hx_nomination_round_start", + ), path( "_hx_voting_rounds", views._hx_voting_rounds, diff --git a/scipost_django/colleges/views.py b/scipost_django/colleges/views.py index 7650a34b3..ec3c3c627 100644 --- a/scipost_django/colleges/views.py +++ b/scipost_django/colleges/views.py @@ -28,6 +28,7 @@ from colleges.permissions import ( is_edadmin_or_advisory_or_active_regular_or_senior_fellow, ) from colleges.utils import check_profile_eligibility_for_fellowship +from scipost.permissions import HTMXResponse from submissions.models import Submission from .constants import ( @@ -762,6 +763,63 @@ def _hx_nominations_needing_specialties(request): ) +@login_required +@user_passes_test(is_edadmin_or_senior_fellow) +def _hx_nominations_no_round_started(request): + nominations_no_round_started = FellowshipNomination.objects.exclude( + profile__specialties__isnull=True + ).filter(voting_rounds__isnull=True) + context = { + "nominations_no_round_started": nominations_no_round_started, + } + return render( + request, + "colleges/_hx_nominations_no_round_started.html", + context, + ) + + +@login_required +@user_passes_test(is_edadmin_or_senior_fellow) +def _hx_nomination_eligible_voters(request, nomination_id): + nomination = FellowshipNomination.objects.get(pk=nomination_id) + eligible_voters = nomination.get_eligible_voters + context = { + "nomination": nomination, + "eligible_voters": eligible_voters, + "nominee_specialties": nomination.profile.specialties.all(), + } + return render( + request, + "colleges/_hx_nomination_eligible_voters.html", + context, + ) + + +def _hx_nomination_round_start(request, nomination_id): + """Create a voting round for the specified nomination using the default eligible voters.""" + nomination = get_object_or_404(FellowshipNomination, pk=nomination_id) + # Find ongoing round for this nomination, if any + ongoing_round = nomination.voting_rounds.ongoing().first() + if ongoing_round: + return HTMXResponse( + f"Round for {nomination.profile} already ongoing until {ongoing_round.voting_deadline}", + tag="danger", + ) + voting_round = FellowshipNominationVotingRound( + nomination=nomination, + voting_opens=timezone.now(), + voting_deadline=timezone.now() + datetime.timedelta(days=14), + ) + voting_round.save() + voting_round.eligible_to_vote.set(nomination.get_eligible_voters) + voting_round.save() + return HTMXResponse( + f"Started round for {nomination.profile} from now until {voting_round.voting_deadline}", + tag="success", + ) + + @login_required @user_passes_test(is_edadmin_or_advisory_or_active_regular_or_senior_fellow) def _hx_nominations(request): -- GitLab