From dddbba7a97944cea4a38bc8e469457f325be17af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Caux?= <git@jscaux.org> Date: Sun, 30 Jan 2022 11:18:00 +0100 Subject: [PATCH] Display voting rounds --- scipost_django/colleges/managers.py | 11 +++++ scipost_django/colleges/models/nomination.py | 14 ++++-- .../templates/colleges/_hx_nomination_li.html | 11 ++++- .../_hx_nomination_voting_rounds.html | 9 ++++ .../templates/colleges/nominations.html | 48 ++++++++++++++++++- scipost_django/colleges/urls.py | 5 ++ scipost_django/colleges/views.py | 38 +++++++++++++-- scipost_django/scipost/models.py | 16 +++++++ scipost_django/scipost/views.py | 5 +- 9 files changed, 144 insertions(+), 13 deletions(-) create mode 100644 scipost_django/colleges/templates/colleges/_hx_nomination_voting_rounds.html diff --git a/scipost_django/colleges/managers.py b/scipost_django/colleges/managers.py index 752aa1df1..dd0a55820 100644 --- a/scipost_django/colleges/managers.py +++ b/scipost_django/colleges/managers.py @@ -98,3 +98,14 @@ class PotentialFellowshipQuerySet(models.QuerySet): Q(in_agreement__in=[contributor]) | Q(in_abstain__in=[contributor]) | Q(in_disagreement__in=[contributor])) + + +class FellowshipNominationVotingRoundQuerySet(models.QuerySet): + + def ongoing(self): + now = timezone.now() + return self.filter(voting_opens__lte=now, voting_deadline__gte=now) + + def closed(self): + now = timezone.now() + return self.filter(voting_deadline__lte=now) diff --git a/scipost_django/colleges/models/nomination.py b/scipost_django/colleges/models/nomination.py index 50d1e8e81..c40687151 100644 --- a/scipost_django/colleges/models/nomination.py +++ b/scipost_django/colleges/models/nomination.py @@ -5,6 +5,8 @@ __license__ = "AGPL v3" from django.db import models from django.utils import timezone +from ..managers import FellowshipNominationVotingRoundQuerySet + class FellowshipNomination(models.Model): @@ -72,7 +74,7 @@ class FellowshipNominationEvent(models.Model): verbose_name_plural = 'Fellowhip Nomination Events' def __str__(self): - return f'Event for {nomination}' + return f'Event for {self.nomination}' class FellowshipNominationVotingRound(models.Model): @@ -93,6 +95,8 @@ class FellowshipNominationVotingRound(models.Model): voting_deadline = models.DateTimeField() + objects = FellowshipNominationVotingRoundQuerySet.as_manager() + class Meta: ordering = [ 'nomination__profile__last_name' @@ -100,8 +104,8 @@ class FellowshipNominationVotingRound(models.Model): verbose_name_plural = 'Fellowship Nomination Voting Rounds' def __str__(self): - return (f'Voting round ({voting_opens.strftime("%Y-%m-%d")} -' - f' {voting_deadline.strftime("%Y-%m-%d")}) for {nomination}') + return (f'Voting round ({self.voting_opens.strftime("%Y-%m-%d")} -' + f' {self.voting_deadline.strftime("%Y-%m-%d")}) for {self.nomination}') class FellowshipNominationVote(models.Model): @@ -177,7 +181,7 @@ class FellowshipNominationDecision(models.Model): verbose_name_plural = 'Fellowship Nomination Decisions' def __str__(self): - return f'Decision for {nomination}: {self.get_outcome_display()}' + return f'Decision for {self.nomination}: {self.get_outcome_display()}' @property def elected(self): @@ -231,7 +235,7 @@ class FellowshipInvitation(models.Model): verbose_name_plural = 'Fellowship Invitations' def __str__(self): - return f'Invitation for {nomination}' + return f'Invitation for {self.nomination}' @property def declined(self): diff --git a/scipost_django/colleges/templates/colleges/_hx_nomination_li.html b/scipost_django/colleges/templates/colleges/_hx_nomination_li.html index 4848bd37a..473ba6d78 100644 --- a/scipost_django/colleges/templates/colleges/_hx_nomination_li.html +++ b/scipost_django/colleges/templates/colleges/_hx_nomination_li.html @@ -1,7 +1,16 @@ <div class="border border-dark"> <details> <summary class="bg-light p-2"> - {{ nomination.profile }}<span class="float-end">{{ nomination.college }}</span> + {{ nomination.profile }} + <span class="float-end"> + {{ nomination.college }} + <span class="ms-4">Outcome:</span> + {% if nomination.decision %} + {{ nomination.decision.get_outcome_display }} + {% else %} + pending + {% endif %} + </span> </summary> <div class="p-2"> <p>Nominated by {{ nomination.nominated_by }} on {{ nomination.nominated_on|date:"Y-m-d" }}</p> diff --git a/scipost_django/colleges/templates/colleges/_hx_nomination_voting_rounds.html b/scipost_django/colleges/templates/colleges/_hx_nomination_voting_rounds.html new file mode 100644 index 000000000..a8657af4b --- /dev/null +++ b/scipost_django/colleges/templates/colleges/_hx_nomination_voting_rounds.html @@ -0,0 +1,9 @@ +<ul> + {% for round in voting_rounds %} + <li class="p-2 mb-2" id="voting_round_{{ round.id }}"> + {{ round }} + </li> + {% empty %} + <li>No voting round found</li> + {% endfor %} +</ul> diff --git a/scipost_django/colleges/templates/colleges/nominations.html b/scipost_django/colleges/templates/colleges/nominations.html index 621dec805..babe3db49 100644 --- a/scipost_django/colleges/templates/colleges/nominations.html +++ b/scipost_django/colleges/templates/colleges/nominations.html @@ -1,5 +1,6 @@ {% extends 'colleges/base.html' %} +{% load user_groups %} {% load crispy_forms_tags %} {% block breadcrumb_items %} @@ -13,11 +14,13 @@ {% block content %} + {% is_ed_admin request.user as is_ed_admin %} + <h1 class="highlight">Fellowship Nominations</h1> <details class="border border-success border-2 mt-4"> <summary class="bg-success bg-opacity-10 p-2"> - <h2>Nominate</h2> + <h2 class="ms-2">Nominate</h2> </summary> <div class="p-2"> <div class="row"> @@ -70,9 +73,50 @@ </div> </details> + <details class="border border-primary border-2 mt-4"> + <summary class="bg-primary bg-opacity-10 p-2"> + <h2 class="ms-2">Vote</h2> + </summary> + <div class="p-2 mt-2"> + {% if is_ed_admin %} + <h3>Ongoing elections</h3> + <div id="voting_rounds_ongoing" + hx-get="{% url 'colleges:_hx_nomination_voting_rounds' %}?filters=ongoing" + hx-trigger="revealed" + > + </div> + <h3>Closed elections</h3> + <div id="voting_rounds_closed" + hx-get="{% url 'colleges:_hx_nomination_voting_rounds' %}?filters=closed" + hx-trigger="revealed" + > + </div> + {% else %} + <h3>Cast your vote (election ongoing)</h3> + <div id="voting_rounds_ongoing_vote_required" + hx-get="{% url 'colleges:_hx_nomination_voting_rounds' %}?filters=ongoing,vote_required" + hx-trigger="revealed" + > + </div> + <h3>Votes you have cast (election ongoing)</h3> + <div id="voting_rounds_ongoing_voted" + hx-get="{% url 'colleges:_hx_nomination_voting_rounds' %}?filters=ongoing,voted" + hx-trigger="revealed" + > + </div> + <h3>Votes you have cast (election closed)</h3> + <div id="voting_rounds_closed_voted" + hx-get="{% url 'colleges:_hx_nomination_voting_rounds' %}?filters=closed,voted" + hx-trigger="revealed" + > + </div> + {% endif %} + </div> + </details> + <details class="border border-2 mt-4"> <summary class="bg-light p-2"> - <h2>List / filter</h2> + <h2 class="ms-2">List / filter</h2> </summary> <div class="p-2 mt-2"> <form diff --git a/scipost_django/colleges/urls.py b/scipost_django/colleges/urls.py index c7d5a8a63..62d830942 100644 --- a/scipost_django/colleges/urls.py +++ b/scipost_django/colleges/urls.py @@ -175,4 +175,9 @@ urlpatterns = [ views._hx_nominations, name='_hx_nominations' ), + path( + '_hx_nomination_voting_rounds', + views._hx_nomination_voting_rounds, + name='_hx_nomination_voting_rounds' + ), ] diff --git a/scipost_django/colleges/views.py b/scipost_django/colleges/views.py index 1f7a37a7f..71838581a 100644 --- a/scipost_django/colleges/views.py +++ b/scipost_django/colleges/views.py @@ -37,7 +37,11 @@ from .forms import ( PotentialFellowshipForm, PotentialFellowshipStatusForm, PotentialFellowshipEventForm, FellowshipNominationForm, FellowshipNominationSearchForm, ) -from .models import College, Fellowship, PotentialFellowship, PotentialFellowshipEvent +from .models import ( + College, Fellowship, + PotentialFellowship, PotentialFellowshipEvent, + FellowshipNominationVotingRound +) from scipost.forms import EmailUsersForm, SearchTextForm from scipost.mixins import PermissionsMixin, PaginationMixin, RequestViewMixin @@ -532,7 +536,6 @@ class PotentialFellowshipEventCreateView(PermissionsMixin, CreateView): ############### -@login_required @user_passes_test(is_edadmin_or_active_regular_or_senior_fellow) def nominations(request): """ @@ -552,7 +555,6 @@ def nominations(request): return render(request, 'colleges/nominations.html', context) -@login_required @user_passes_test(is_edadmin_or_active_regular_or_senior_fellow) def _hx_nomination_form(request, profile_id): profile = get_object_or_404(Profile, pk=profile_id) @@ -583,7 +585,6 @@ def _hx_nomination_form(request, profile_id): return render(request, 'colleges/_hx_nomination_form.html', context) -@login_required @user_passes_test(is_edadmin_or_active_regular_or_senior_fellow) def _hx_nominations(request): form = FellowshipNominationSearchForm(request.POST or None) @@ -596,3 +597,32 @@ def _hx_nominations(request): page_obj = paginator.get_page(page_nr) context = { 'page_obj': page_obj } return render(request, 'colleges/_hx_nominations.html', context) + + +@user_passes_test(is_edadmin_or_active_regular_or_senior_fellow) +def _hx_nomination_voting_rounds(request): + fellowship = request.user.contributor.session_fellowship(request) + filters = request.GET.get('filters', None) + if filters: + filters = filters.split(',') + if not filters: # if no filters present, return empty response + voting_rounds = FellowshipNominationVotingRound.objects.none() + else: + voting_rounds = FellowshipNominationVotingRound.objects.all() + for filter in filters: + if filter == 'ongoing': + voting_rounds = voting_rounds.ongoing() + if filter == 'closed': + voting_rounds = voting_rounds.closed() + if filter == 'vote_required': + # show all voting rounds to edadmin; for Fellow, filter + if not request.user.contributor.is_ed_admin: + voting_rounds = voting_rounds.filter( + eligible_to_vote=fellowship + ).exclude(votes__fellow=fellowship) + if filter == 'voted': + voting_rounds = voting_rounds.filter(votes__fellow=fellowship) + context = { + 'voting_rounds': voting_rounds, + } + return render(request, 'colleges/_hx_nomination_voting_rounds.html', context) diff --git a/scipost_django/scipost/models.py b/scipost_django/scipost/models.py index bd4678e12..6b9311eb3 100644 --- a/scipost_django/scipost/models.py +++ b/scipost_django/scipost/models.py @@ -159,6 +159,22 @@ class Contributor(models.Model): def is_active_senior_fellow(self): return self.fellowships.active().senior().exists() + def session_fellowship(self, request): + """Return session's fellowship, if any; if Fellow, set session_fellowship_id if not set.""" + fellowships = self.fellowships.active() + if fellowships.exists(): + if request.session['session_fellowship_id']: + from colleges.models import Fellowship + try: + return self.fellowships.active().get(pk=request.session['session_fellowship_id']) + except Fellowship.DoesNotExist: + return None + # set the session's fellowship_id to default + fellowship = fellowships.first() + request.session['session_fellowship_id'] = fellowship.id + return fellowship + return None + @property def is_vetting_editor(self): """Check if Contributor is a Vetting Editor.""" diff --git a/scipost_django/scipost/views.py b/scipost_django/scipost/views.py index f40847a4a..8ea73a998 100644 --- a/scipost_django/scipost/views.py +++ b/scipost_django/scipost/views.py @@ -730,10 +730,13 @@ class SciPostLoginView(LoginView): return self.request.GET def get_success_url(self): - """Add the `acad_field_view` item to session.""" + """Add items to session variables.""" self.request.session['session_acad_field_slug'] = \ self.request.user.contributor.profile.acad_field.slug if \ self.request.user.contributor.profile.acad_field else '' + if self.request.user.contributor.fellowships.active(): + self.request.session['session_fellowship_id'] = \ + self.request.user.contributor.fellowships.active().first().id return super().get_success_url() def get_redirect_url(self): -- GitLab