diff --git a/scipost_django/colleges/admin.py b/scipost_django/colleges/admin.py index bb36f032bfad8c11d499e87a33ec86e6798af193..316320cdbf4c4aa8df393a863e65c5b669e0e1b6 100644 --- a/scipost_django/colleges/admin.py +++ b/scipost_django/colleges/admin.py @@ -101,11 +101,11 @@ class FellowshipNominationAdmin(admin.ModelAdmin): FellowshipNominationEventInline, FellowshipNominationCommentInline, FellowshipNominationVotingRoundInline, - FellowshipNominationDecisionInline, FellowshipInvitationInline, ] - list_display = ["college", "profile", "nominated_on"] - search_fields = ["college", "profile"] + list_filter = ["college__name"] + list_display = ["profile", "college", "nominated_on"] + search_fields = ["college__name", "profile__last_name", "profile__first_name"] autocomplete_fields = ["profile", "nominated_by", "fellowship"] @@ -121,11 +121,35 @@ class FellowshipNominationVotingRoundAdmin(admin.ModelAdmin): model = FellowshipNominationVotingRound inlines = [ FellowshipNominationVoteInline, + FellowshipNominationDecisionInline, + ] + search_fields = [ + "nomination__profile__last_name", + "nomination__profile__first_name", + "nomination__college__name", + ] + list_display = [ + "nomination", + "voting_opens", + "voting_deadline", + "is_open_checkmark", + "decision__outcome", ] autocomplete_fields = [ "nomination", "eligible_to_vote", ] + list_filter = ("decision__outcome",) + + def decision__outcome(self, obj): + return obj.decision.get_outcome_display() + + @admin.display( + boolean=True, + description="Open", + ) + def is_open_checkmark(self, obj): + return obj.is_open admin.site.register( diff --git a/scipost_django/colleges/forms.py b/scipost_django/colleges/forms.py index 38e304c7182b9a8adfb2cd9b247f30e06792d466..01a1752828d144e6d0a0703598d1168fc5e6b20a 100644 --- a/scipost_django/colleges/forms.py +++ b/scipost_django/colleges/forms.py @@ -547,8 +547,8 @@ class FellowshipNominationCommentForm(forms.ModelForm): class FellowshipNominationDecisionForm(forms.ModelForm): class Meta: model = FellowshipNominationDecision - fields = [ - "nomination", + fields: list[str] = [ + "voting_round", "outcome", "fixed_on", "comments", @@ -558,7 +558,7 @@ class FellowshipNominationDecisionForm(forms.ModelForm): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.layout = Layout( - Field("nomination", type="hidden"), + Field("voting_round", type="hidden"), Field("fixed_on", type="hidden"), Div( Div(Field("comments"), css_class="col-8"), diff --git a/scipost_django/colleges/managers.py b/scipost_django/colleges/managers.py index a7f0ef26c79c536883fc3fb6062109afafb82e2e..bbea0608e8fd7513555ceddf86d5a2fb0f207e2b 100644 --- a/scipost_django/colleges/managers.py +++ b/scipost_django/colleges/managers.py @@ -127,7 +127,9 @@ class PotentialFellowshipQuerySet(models.QuerySet): class FellowshipNominationQuerySet(models.QuerySet): def needing_handling(self): - return self.exclude(decision__isnull=False).exclude(voting_rounds__isnull=False) + return self.exclude(voting_rounds__isnull=False).exclude( + voting_rounds__decision__isnull=False + ) class FellowshipNominationVotingRoundQuerySet(models.QuerySet): diff --git a/scipost_django/colleges/migrations/0040_auto_20230719_2108.py b/scipost_django/colleges/migrations/0040_auto_20230719_2108.py new file mode 100644 index 0000000000000000000000000000000000000000..ffa1982e87caadc7d5b161bac4178124dc981432 --- /dev/null +++ b/scipost_django/colleges/migrations/0040_auto_20230719_2108.py @@ -0,0 +1,56 @@ +# Generated by Django 3.2.18 on 2023-07-19 19:08 + +from django.db import migrations, models +import django.db.models.deletion + + +def copy_decision_from_nomination_to_voting_round(apps, schema_editor): + FellowshipNominationDecision = apps.get_model( + "colleges", "FellowshipNominationDecision" + ) + for decision in FellowshipNominationDecision.objects.all(): + decision.voting_round = decision.nomination.voting_rounds.first() + decision.save() + + +def copy_decision_from_voting_round_to_nomination(apps, schema_editor): + FellowshipNominationDecision = apps.get_model( + "colleges", "FellowshipNominationDecision" + ) + for decision in FellowshipNominationDecision.objects.all(): + decision.nomination = decision.voting_round.nomination + decision.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("colleges", "0039_nomination_add_events"), + ] + + operations = [ + migrations.AlterModelOptions( + name="fellowshipnominationdecision", + options={ + "ordering": ["voting_round"], + "verbose_name_plural": "Fellowship Nomination Decisions", + }, + ), + migrations.AddField( + model_name="fellowshipnominationdecision", + name="voting_round", + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="decision", + to="colleges.fellowshipnominationvotinground", + ), + ), + migrations.RunPython( + copy_decision_from_nomination_to_voting_round, + copy_decision_from_voting_round_to_nomination, + ), + migrations.RemoveField( + model_name="fellowshipnominationdecision", + name="nomination", + ), + ] diff --git a/scipost_django/colleges/models/nomination.py b/scipost_django/colleges/models/nomination.py index d908ea8f785e7165b5d06024527275af79b5dce0..ee40c39d655d35be14959b865d0ef4c800e9d9b7 100644 --- a/scipost_django/colleges/models/nomination.py +++ b/scipost_django/colleges/models/nomination.py @@ -81,6 +81,11 @@ class FellowshipNomination(models.Model): def latest_voting_round(self): return self.voting_rounds.first() + @property + def decision(self): + """The singular non-deprecated decision for this nomination.""" + return self.latest_voting_round.decision + @property def decision_blocks(self): """ @@ -192,6 +197,10 @@ class FellowshipNominationVotingRound(models.Model): return vote.vote return None + @property + def is_open(self): + return self.voting_opens <= timezone.now() <= self.voting_deadline + class FellowshipNominationVote(models.Model): VOTE_AGREE = "agree" @@ -235,10 +244,12 @@ class FellowshipNominationVote(models.Model): class FellowshipNominationDecision(models.Model): - nomination = models.OneToOneField( - "colleges.FellowshipNomination", + voting_round = models.OneToOneField( + "colleges.FellowshipNominationVotingRound", on_delete=models.CASCADE, related_name="decision", + null=True, + blank=True, ) OUTCOME_ELECTED = "elected" @@ -261,12 +272,12 @@ class FellowshipNominationDecision(models.Model): class Meta: ordering = [ - "nomination", + "voting_round", ] verbose_name_plural = "Fellowship Nomination Decisions" def __str__(self): - return f"Decision for {self.nomination}: {self.get_outcome_display()}" + return f"Decision for {self.voting_round}: {self.get_outcome_display()}" @property def elected(self): diff --git a/scipost_django/colleges/templates/colleges/_hx_nomination_decision.html b/scipost_django/colleges/templates/colleges/_hx_nomination_decision.html deleted file mode 100644 index 8444d39ab90954534d8af36e5958e0bf801ba3e0..0000000000000000000000000000000000000000 --- a/scipost_django/colleges/templates/colleges/_hx_nomination_decision.html +++ /dev/null @@ -1,22 +0,0 @@ -{% load crispy_forms_tags %} -{% load automarkup %} -{% if nomination.decision %} - {{ nomination.decision }} - <p>Fixed on: {{ nomination.decision.fixed_on }}</p> - {% if nomination.decision.comments %} - <p>Comments:</p> - {% automarkup nomination.decision.comments %} - {% endif %} -{% else %} - {% with blocks=nomination.decision_blocks %} - {% if blocks %} - <p>The decision cannot be fixed at this moment: {{ blocks }}</p> - {% else %} - <form hx-post="{% url 'colleges:_hx_nomination_decision' nomination_id=nomination.id %}" - hx-target="#nomination-{{ nomination.id }}-decision" - > - {% crispy decision_form %} - </form> - {% endif %} - {% endwith %} -{% endif %} diff --git a/scipost_django/colleges/templates/colleges/_hx_nomination_decision_form.html b/scipost_django/colleges/templates/colleges/_hx_nomination_decision_form.html new file mode 100644 index 0000000000000000000000000000000000000000..8944c9831ba8af1fd160944092c01817ee612dd2 --- /dev/null +++ b/scipost_django/colleges/templates/colleges/_hx_nomination_decision_form.html @@ -0,0 +1,26 @@ +{% load crispy_forms_tags %} +{% load automarkup %} + +{% if voting_round.decision %} + {{ voting_round.decision }} + <p>Fixed on: {{ voting_round.decision.fixed_on }}</p> + + {% if voting_round.decision.comments %} + <p>Comments:</p> + {% automarkup voting_round.decision.comments %} + {% endif %} + +{% else %} + {% with blocks=voting_round.decision_blocks %} + + {% if blocks %} + <p>The decision cannot be fixed at this moment: {{ blocks }}</p> + {% else %} + <form hx-post="{% url 'colleges:_hx_nomination_decision_form' round_id=voting_round.id %}" + hx-target="#nomination-{{ voting_round.id }}-decision"> + {% crispy decision_form %} + </form> + {% endif %} + + {% endwith %} +{% endif %} diff --git a/scipost_django/colleges/urls.py b/scipost_django/colleges/urls.py index 8743084fab237623ce66c3762927c22d209cfcaa..e1195b5d3ccc043314e97ddfd8a4c2f51df0301b 100644 --- a/scipost_django/colleges/urls.py +++ b/scipost_django/colleges/urls.py @@ -195,9 +195,9 @@ urlpatterns = [ name="_hx_nomination_vote", ), path( - "_hx_nomination_decision/<int:nomination_id>", - views._hx_nomination_decision, - name="_hx_nomination_decision", + "_hx_nomination_decision_form/<int:round_id>", + views._hx_nomination_decision_form, + name="_hx_nomination_decision_form", ), path( "_hx_nominations_invitations", diff --git a/scipost_django/colleges/views.py b/scipost_django/colleges/views.py index 4489a90a019b4c8ef28c7253eefa3f758921a41d..c99eb44880ee65dd55ff888c9789804366158d49 100644 --- a/scipost_django/colleges/views.py +++ b/scipost_django/colleges/views.py @@ -833,14 +833,14 @@ def _hx_voting_rounds(request): if "closed" in selected: voting_rounds = voting_rounds.closed() if "-pending" in selected: - voting_rounds = voting_rounds.filter(nomination__decision__isnull=True) + voting_rounds = voting_rounds.filter(decision__isnull=True) if "-elected" in selected: voting_rounds = voting_rounds.filter( - nomination__decision__outcome=FellowshipNominationDecision.OUTCOME_ELECTED + decision__outcome=FellowshipNominationDecision.OUTCOME_ELECTED ) if "-notelected" in selected: voting_rounds = voting_rounds.filter( - nomination__decision__outcome=FellowshipNominationDecision.OUTCOME_NOT_ELECTED + decision__outcome=FellowshipNominationDecision.OUTCOME_NOT_ELECTED ) if "vote_required" in selected: # show all voting rounds to edadmin; for Fellow, filter @@ -900,8 +900,9 @@ def _hx_nomination_vote(request, voting_round_id): @login_required @user_passes_test(is_edadmin) -def _hx_nomination_decision(request, nomination_id): - nomination = get_object_or_404(FellowshipNomination, pk=nomination_id) +def _hx_nomination_decision_form(request, round_id): + voting_round = get_object_or_404(FellowshipNominationVotingRound, pk=round_id) + nomination = voting_round.nomination decision_form = FellowshipNominationDecisionForm(request.POST or None) if decision_form.is_valid(): decision = decision_form.save() @@ -916,12 +917,12 @@ def _hx_nomination_decision(request, nomination_id): description="Invitation created", by=request.user.contributor ) else: - decision_form.fields["nomination"].initial = nomination + decision_form.fields["voting_round"].initial = voting_round context = { - "nomination": nomination, + "voting_round": voting_round, "decision_form": decision_form, } - return render(request, "colleges/_hx_nomination_decision.html", context) + return render(request, "colleges/_hx_nomination_decision_form.html", context) @login_required