SciPost Code Repository

Skip to content
Snippets Groups Projects
views.py 46.8 KiB
Newer Older
__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
from dal import autocomplete

Jorran de Wit's avatar
Jorran de Wit committed
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 HttpResponse, Http404
Jorran de Wit's avatar
Jorran de Wit committed
from django.shortcuts import get_object_or_404, render, redirect
from django.utils import timezone
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.list import ListView
Jorran de Wit's avatar
Jorran de Wit committed

from colleges.permissions import (
    is_edadmin_or_senior_fellow,
    is_edadmin_or_advisory_or_active_regular_or_senior_fellow,
from colleges.utils import check_profile_eligibility_for_fellowship
from invitations.constants import INVITATION_EDITORIAL_FELLOW
from invitations.models import RegistrationInvitation
from scipost.constants import TITLE_DR
from scipost.permissions import HTMXPermissionsDenied, HTMXResponse
Jorran de Wit's avatar
Jorran de Wit committed
from submissions.models import Submission
Jorran de Wit's avatar
Jorran de Wit committed
from .constants import (
    POTENTIAL_FELLOWSHIP_STATUSES,
    POTENTIAL_FELLOWSHIP_EVENT_STATUSUPDATED,
    POTENTIAL_FELLOWSHIP_INVITED,
    POTENTIAL_FELLOWSHIP_ACTIVE_IN_COLLEGE,
    potential_fellowship_statuses_dict,
    POTENTIAL_FELLOWSHIP_EVENT_VOTED_ON,
    POTENTIAL_FELLOWSHIP_EVENT_EMAILED,
)
    CollegeChoiceForm,
    FellowshipNominationSearchForm,
    FellowshipNominationVetoForm,
    FellowshipNominationVotingRoundStartForm,
    FellowshipSearchForm,
    FellowshipDynSelForm,
    FellowshipForm,
    FellowshipRemoveSubmissionForm,
    FellowshipAddSubmissionForm,
    FellowshipRemoveProceedingsForm,
    FellowshipAddProceedingsForm,
    PotentialFellowshipForm,
    PotentialFellowshipStatusForm,
    PotentialFellowshipEventForm,
    FellowshipNominationForm,
    FellowshipNominationSearchForm,
    FellowshipNominationCommentForm,
    FellowshipNominationDecisionForm,
    FellowshipInvitationResponseForm,
from .models import (
    College,
    Fellowship,
    PotentialFellowship,
    PotentialFellowshipEvent,
    FellowshipNominationEvent,
    FellowshipNominationVotingRound,
    FellowshipNominationVote,
    FellowshipNominationDecision,
    FellowshipInvitation,
from scipost.forms import EmailUsersForm, SearchTextForm
from scipost.mixins import PermissionsMixin, PaginationMixin, RequestViewMixin
from scipost.models import Contributor
from common.utils import Q_with_alternative_spellings
from mails.views import MailView, MailEditorSubviewHTMX
from ontology.models import Branch
from profiles.models import Profile
from profiles.forms import ProfileDynSelForm
class CollegeListView(ListView):
    model = College

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        context["branches"] = Branch.objects.all()
class CollegeDetailView(DetailView):
    model = College
    template_name = "colleges/college_detail.html"
    def get_object(self, queryset=None):
        """
        Bypass django.views.generic.detail.SingleObjectMixin:
        since CollegeSlugConverter already found the College as a kwarg, just pass that object on.
        """
        return self.kwargs["college"]
class FellowshipAutocompleteView(autocomplete.Select2QuerySetView):
    """
    View to feed the Select2 widget.
    """
    def get_queryset(self):
        qs = Fellowship.objects.all()
        if self.q:
            qs = qs.filter(
                Q(contributor__profile__first_name__icontains=self.q)
                | Q(contributor__profile__last_name__icontains=self.q)
            ).distinct()
class FellowshipCreateView(PermissionsMixin, CreateView):
    """
    Create a new Fellowship instance for an existing Contributor.

    A new Fellowship can be created only for:
    * an existing Fellow who is renewed
    * out of an existing PotentialFellowship (elected, or named by Admin)

    If the elected/named Fellow does not yet have a Contributor object,
    this must be set up first.
    """

    permission_required = "scipost.can_manage_college_composition"
    form_class = FellowshipForm
    template_name = "colleges/fellowship_form.html"

    def get_initial(self):
        initial = super().get_initial()
        contributor = get_object_or_404(
            Contributor, pk=self.kwargs.get("contributor_id")
        )
        initial.update(
            {
                "contributor": contributor.id,
                "start_date": datetime.date.today(),
                "until_date": datetime.date.today()
                + datetime.timedelta(days=int(5 * 365.25)),
            }
        )
        return initial

    def form_valid(self, form):
        """
        Save the new Fellowship, add College rights and update the status of any PotentialFellowship and FellowshipNomination.
        """
        self.object = form.save()
        group = Group.objects.get(name="Editorial College")
        self.object.contributor.user.groups.add(group)
        potfels = PotentialFellowship.objects.filter(
            profile=self.object.contributor.profile
        )
        for potfel in potfels:
            potfelevent = PotentialFellowshipEvent(
                potfel=potfel,
                event=POTENTIAL_FELLOWSHIP_EVENT_STATUSUPDATED,
                comments="Fellowship created for this Potential Fellow",
                noted_on=timezone.now(),
                noted_by=self.request.user.contributor,
            )
            potfelevent.save()
            potfel.status = POTENTIAL_FELLOWSHIP_ACTIVE_IN_COLLEGE
            potfel.save()
        nomination = FellowshipNomination.objects.filter(
            profile=self.object.contributor.profile
        ).first()
        if nomination:
Jean-Sébastien Caux's avatar
Jean-Sébastien Caux committed
            nomination.fellowship = self.object
            nomination.save()
            nomination.add_event(
                description="Fellowship created",
                by=self.request.user.contributor,
            )
        return redirect(self.get_success_url())


class FellowshipUpdateView(PermissionsMixin, UpdateView):
    """
    Update an existing Fellowship.
    """

    permission_required = "scipost.can_manage_college_composition"
    model = Fellowship
    form_class = FellowshipForm
    template_name = "colleges/fellowship_form.html"


class FellowshipDetailView(PermissionsMixin, DetailView):
    permission_required = "scipost.can_manage_college_composition"
    def get_queryset(self):
        queryset = Fellowship.objects.all().prefetch_related(
            "pool__preprint",
            "pool__editor_in_charge",
        )
        return queryset

class FellowshipListView(PermissionsMixin, PaginationMixin, ListView):
    """
    List Fellowship instances (accessible to College managers).
    """

    permission_required = "scipost.can_manage_college_composition"
    model = Fellowship
    paginate_by = 25
    def get_queryset(self):
        """
        Return a queryset of Fellowships filtered by optional GET data.
        """
        queryset = Fellowship.objects.all()
        if self.kwargs.get("acad_field", None):
            queryset = queryset.filter(
                contributor__profile__acad_field=self.kwargs["acad_field"]
            )
            if self.kwargs.get("specialty", None):
                queryset = queryset.filter(
                    contributor__profile__specialties=self.kwargs["specialty"]
                )
        if self.request.GET.get("type", None):
            if self.request.GET.get("type") == "regular":
                queryset = queryset.filter(guest=False)
            elif self.request.GET.get("type") == "guest":
                queryset = queryset.filter(guest=True)
        if self.request.GET.get("text"):
            query = Q_with_alternative_spellings(
                contributor__profile__last_name__istartswith=self.request.GET["text"]
            )
            queryset = queryset.filter(query)
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["searchform"] = SearchTextForm(
            initial={"text": self.request.GET.get("text")}
        )
@permission_required("scipost.can_draft_publication")
def _hx_fellowship_dynsel_list(request):
    form = FellowshipDynSelForm(request.POST or None)
    if form.is_valid():
        fellowships = form.search_results()
    else:
        fellowships = Fellowship.objects.none()
    context = {
        "fellowships": fellowships,
        "action_url_name": form.cleaned_data["action_url_name"],
        "action_url_base_kwargs": form.cleaned_data["action_url_base_kwargs"],
        "action_target_element_id": form.cleaned_data["action_target_element_id"],
    return render(request, "colleges/_hx_fellowship_dynsel_list.html", context)
class FellowshipStartEmailView(PermissionsMixin, MailView):
    """
    After setting up a new Fellowship, send an info email to the new Fellow.
    """

    permission_required = "scipost.can_manage_college_composition"
    queryset = Fellowship.objects.all()
    mail_code = "fellows/email_fellow_fellowship_start"
    success_url = reverse_lazy("colleges:fellowships")
    def form_valid(self, form):
        """Create an event associated to this outgoing email."""
        event = FellowshipNominationEvent(
            nomination=self.object.nomination,
            description="Fellowship start email sent",
            by=self.request.user.contributor,
        )
        event.save()
        return super().form_valid(form)

@permission_required("scipost.can_manage_college_composition", raise_exception=True)
def email_College_Fellows(request, college):
    """
    Send an email to all Fellows within a College.
    """
    user_ids = [
        f.contributor.user.id for f in college.fellowships.regular_or_senior().active()
    ]
    form = EmailUsersForm(request.POST or None, initial={"users": user_ids})
        messages.success(request, "Email sent")
        return redirect(college.get_absolute_url())
    return render(
        request,
        "colleges/email_College_Fellows.html",
        {"form": form, "college": college},
    )
Jorran de Wit's avatar
Jorran de Wit committed
@login_required
@user_passes_test(is_edadmin_or_senior_fellow)
def _hx_submission_add_fellowship(request, identifier_w_vn_nr):
    """Add Fellowship to a Submission's Fellowship."""
    submission = get_object_or_404(
        Submission.objects.in_pool(request.user),
        preprint__identifier_w_vn_nr=identifier_w_vn_nr,
Jorran de Wit's avatar
Jorran de Wit committed
    form = SubmissionAddFellowshipForm(request.POST or None, instance=submission)

    if form.is_valid():
        form.save()
        messages.success(
            request,
            "Fellowship {fellowship} ({id}) added to Submission.".format(
                fellowship=form.cleaned_data["fellowship"].contributor,
                id=form.cleaned_data["fellowship"].id,
            ),
        )
Jean-Sébastien Caux's avatar
Jean-Sébastien Caux committed
            "submissions/pool/_submission_fellows.html",
            context={
                "submission": submission,
            },
Jorran de Wit's avatar
Jorran de Wit committed
    context = {
        "submission": submission,
        "form": form,
Jorran de Wit's avatar
Jorran de Wit committed
    }
    return render(request, "colleges/_hx_submission_add_fellowship.html", context)
Jorran de Wit's avatar
Jorran de Wit committed


@login_required
@user_passes_test(is_edadmin)
def _hx_submission_remove_fellowship(request, identifier_w_vn_nr, pk):
    """Remove Fellowship from a Submission's Fellowship."""
    submission = get_object_or_404(
        Submission.objects.in_pool(request.user),
        preprint__identifier_w_vn_nr=identifier_w_vn_nr,
    )
    submission.fellows.remove(pk)
    return render(
        request,
        "submissions/pool/_submission_fellows.html",
Jean-Sébastien Caux's avatar
Jean-Sébastien Caux committed
        context={
            "submission": submission,
        },
Jorran de Wit's avatar
Jorran de Wit committed
@login_required
@permission_required("scipost.can_manage_college_composition", raise_exception=True)
def fellowship_remove_submission(request, id, identifier_w_vn_nr):
    """Remove Submission from the Fellowship."""
Jorran de Wit's avatar
Jorran de Wit committed
    fellowship = get_object_or_404(Fellowship, id=id)
    submission = get_object_or_404(
        fellowship.pool.all(), preprint__identifier_w_vn_nr=identifier_w_vn_nr
    )
    form = FellowshipRemoveSubmissionForm(
        request.POST or None, submission=submission, instance=fellowship
    )
Jorran de Wit's avatar
Jorran de Wit committed

    if form.is_valid() and request.POST:
        form.save()
        messages.success(
            request,
            "Submission {submission_id} removed from Fellowship.".format(
                submission_id=identifier_w_vn_nr
            ),
        )
Jorran de Wit's avatar
Jorran de Wit committed
        return redirect(fellowship.get_absolute_url())

    context = {"fellowship": fellowship, "form": form, "submission": submission}
    return render(request, "colleges/fellowship_submission_remove.html", context)
Jorran de Wit's avatar
Jorran de Wit committed


@login_required
@permission_required("scipost.can_manage_college_composition", raise_exception=True)
Jorran de Wit's avatar
Jorran de Wit committed
def fellowship_add_submission(request, id):
    """Add Submission to the pool of a Fellowship."""
Jorran de Wit's avatar
Jorran de Wit committed
    fellowship = get_object_or_404(Fellowship, id=id)
    form = FellowshipAddSubmissionForm(request.POST or None, instance=fellowship)

    if form.is_valid():
        form.save()
        messages.success(
            request,
            "Submission {submission_id} added to Fellowship.".format(
                submission_id=form.cleaned_data[
                    "submission"
                ].preprint.identifier_w_vn_nr
            ),
        )
Jorran de Wit's avatar
Jorran de Wit committed
        return redirect(fellowship.get_absolute_url())

    context = {
        "fellowship": fellowship,
        "form": form,
    return render(request, "colleges/fellowship_submission_add.html", context)
Jorran de Wit's avatar
Jorran de Wit committed


@login_required
@permission_required("scipost.can_manage_college_composition", raise_exception=True)
Jorran de Wit's avatar
Jorran de Wit committed
def fellowship_remove_proceedings(request, id, proceedings_id):
    """
    Remove Proceedings from the pool of a Fellowship.
    """
    fellowship = get_object_or_404(Fellowship, id=id)
    proceedings = get_object_or_404(fellowship.proceedings.all(), id=proceedings_id)
    form = FellowshipRemoveProceedingsForm(
        request.POST or None, proceedings=proceedings, instance=fellowship
    )
Jorran de Wit's avatar
Jorran de Wit committed

    if form.is_valid() and request.POST:
        form.save()
        messages.success(
            request, "Proceedings %s removed from Fellowship." % str(proceedings)
        )
Jorran de Wit's avatar
Jorran de Wit committed
        return redirect(fellowship.get_absolute_url())

    context = {"fellowship": fellowship, "form": form, "proceedings": proceedings}
    return render(request, "colleges/fellowship_proceedings_remove.html", context)
Jorran de Wit's avatar
Jorran de Wit committed


@login_required
@permission_required("scipost.can_manage_college_composition", raise_exception=True)
Jorran de Wit's avatar
Jorran de Wit committed
def fellowship_add_proceedings(request, id):
    """
    Add Proceedings to the pool of a Fellowship.
    """
    fellowship = get_object_or_404(Fellowship, id=id)
    form = FellowshipAddProceedingsForm(request.POST or None, instance=fellowship)

    if form.is_valid():
        form.save()
        proceedings = form.cleaned_data.get("proceedings", "")
        messages.success(
            request, "Proceedings %s added to Fellowship." % str(proceedings)
        )
Jorran de Wit's avatar
Jorran de Wit committed
        return redirect(fellowship.get_absolute_url())

    context = {
        "fellowship": fellowship,
        "form": form,
Jorran de Wit's avatar
Jorran de Wit committed
    }
    return render(request, "colleges/fellowship_proceedings_add.html", context)
@login_required
@user_passes_test(is_edadmin_or_senior_fellow)
def fellowships_monitor(request):
    """
    Dashboard providing an overview of Fellows' activity levels (forlevels of activity. EdAdmin and SF).
    """
    context = {
        "college_choice_form": CollegeChoiceForm(),
    }
    return render(request, "colleges/fellowships_monitor.html", context)


@login_required
@user_passes_test(is_edadmin_or_senior_fellow)
def _hx_fellowships_monitor_college_choice(request):
    if request.method == "POST":
        form = CollegeChoiceForm(request.POST)
        if form.is_valid():
            fellowships_search_form = FellowshipSearchForm(
                initial={"college": form.cleaned_data["college"]},
            )
            context = {
                "fellowships_search_form": fellowships_search_form,
            }
            return render(request, "colleges/_hx_fellowships_search_form.html", context)


@login_required
@user_passes_test(is_edadmin_or_senior_fellow)
def _hx_fellowships_monitor(request):
    if request.method == "POST":
        form = FellowshipSearchForm(request.POST)
        if form.is_valid():
            fellowships = form.search_results()
        else:
            fellowships = Fellowship.objects.active()
        paginator = Paginator(fellowships, 16)
        page_nr = request.GET.get("page")
        page_obj = paginator.get_page(page_nr)
        context = {"page_obj": page_obj}
        return render(request, "colleges/_hx_fellowships_monitor.html", context)


#########################
# Potential Fellowships #
#########################
class PotentialFellowshipCreateView(PermissionsMixin, RequestViewMixin, CreateView):
    """
    Formview to create a new Potential Fellowship.
    """

    permission_required = "scipost.can_add_potentialfellowship"
    form_class = PotentialFellowshipForm
    template_name = "colleges/potentialfellowship_form.html"
    success_url = reverse_lazy("colleges:potential_fellowships")
class PotentialFellowshipUpdateView(PermissionsMixin, RequestViewMixin, UpdateView):
    """
    Formview to update a Potential Fellowship.
    """

    permission_required = "scipost.can_manage_college_composition"
    model = PotentialFellowship
    form_class = PotentialFellowshipForm
    template_name = "colleges/potentialfellowship_form.html"
    success_url = reverse_lazy("colleges:potential_fellowships")


class PotentialFellowshipUpdateStatusView(PermissionsMixin, UpdateView):
    """
    Formview to update the status of a Potential Fellowship.
    """

    permission_required = "scipost.can_manage_college_composition"
    fields = ["status"]
    success_url = reverse_lazy("colleges:potential_fellowships")

    def form_valid(self, form):
        event = PotentialFellowshipEvent(
            potfel=self.object,
            event=POTENTIAL_FELLOWSHIP_EVENT_STATUSUPDATED,
            comments=(
                "Status updated to %s"
                % potential_fellowship_statuses_dict[form.cleaned_data["status"]]
            ),
            noted_by=self.request.user.contributor,
        )
        event.save()
        return super().form_valid(form)


class PotentialFellowshipDeleteView(PermissionsMixin, DeleteView):
    """
    Delete a Potential Fellowship.
    """

    permission_required = "scipost.can_manage_college_composition"
    success_url = reverse_lazy("colleges:potential_fellowships")
class PotentialFellowshipListView(PermissionsMixin, PaginationMixin, ListView):
    """
    List the PotentialFellowship object instances.
    """

    permission_required = "scipost.can_view_potentialfellowship_list"
    model = PotentialFellowship
    paginate_by = 25

    def get_queryset(self):
        """
        Return a queryset of PotentialFellowships using optional GET data.
        """
        queryset = PotentialFellowship.objects.all()
        # Admin and EdAdmin see all
        # while Advisory Board and (Senior) Fellows see their field by default
        # if they have not specified another field
        acad_field = None
        if not (
            self.request.user.contributor.is_scipost_admin
            or self.request.user.contributor.is_ed_admin
        ):
            acad_field = self.request.user.contributor.profile.acad_field
        acad_field = self.kwargs.get("acad_field", None) or acad_field
        if acad_field:
            queryset = queryset.filter(profile__acad_field=acad_field)
            if self.kwargs.get("specialty", None):
                queryset = queryset.filter(
                    profile__specialties=self.kwargs["specialty"]
                )
        if self.request.GET.get("status", None):
            queryset = queryset.filter(status=self.request.GET.get("status"))
        if self.request.GET.get("text"):
            query = Q_with_alternative_spellings(
                profile__last_name__istartswith=self.request.GET["text"]
            )
            queryset = queryset.filter(query)
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["potfels_to_vote_on"] = PotentialFellowship.objects.to_vote_on(
            self.request.user.contributor
        )
        context["potfels_voted_on"] = PotentialFellowship.objects.voted_on(
            self.request.user.contributor
        )
        context["statuses"] = POTENTIAL_FELLOWSHIP_STATUSES
        context["searchform"] = SearchTextForm(
            initial={"text": self.request.GET.get("text")}
        )
        return context


class PotentialFellowshipDetailView(PermissionsMixin, DetailView):
    permission_required = "scipost.can_view_potentialfellowship_list"
    model = PotentialFellowship

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        context["pfstatus_form"] = PotentialFellowshipStatusForm(
            initial={"status": self.object.status}
        )
        context["pfevent_form"] = PotentialFellowshipEventForm()
@permission_required("scipost.can_vote_on_potentialfellowship", raise_exception=True)
def vote_on_potential_fellowship(request, potfel_id, vote):
    potfel = get_object_or_404(PotentialFellowship, pk=potfel_id)
    if not potfel.can_vote(request.user):
        raise Http404
    potfel.in_agreement.remove(request.user.contributor)
    potfel.in_abstain.remove(request.user.contributor)
    potfel.in_disagreement.remove(request.user.contributor)
        potfel.in_agreement.add(request.user.contributor)
        comments = "Voted Agree"
    elif vote == "N":
        potfel.in_abstain.add(request.user.contributor)
        comments = "Voted Abstain"
    elif vote == "D":
        potfel.in_disagreement.add(request.user.contributor)
        comments = "Voted Disagree"
    else:
        raise Http404
    newevent = PotentialFellowshipEvent(
        potfel=potfel,
        event=POTENTIAL_FELLOWSHIP_EVENT_VOTED_ON,
        comments=comments,
        noted_by=request.user.contributor,
    )
    newevent.save()
    return redirect(reverse("colleges:potential_fellowships"))
class PotentialFellowshipInitialEmailView(PermissionsMixin, MailView):
    """Send a templated email to a Potential Fellow."""

    permission_required = "scipost.can_manage_college_composition"
    queryset = PotentialFellowship.objects.all()
    mail_code = "potentialfellowships/invite_potential_fellow_initial"
    success_url = reverse_lazy("colleges:potential_fellowships")

    def form_valid(self, form):
        """Create an event associated to this outgoing email."""
        event = PotentialFellowshipEvent(
            potfel=self.object,
            event=POTENTIAL_FELLOWSHIP_EVENT_EMAILED,
            comments="Emailed initial template to potential Fellow",
            noted_by=self.request.user.contributor,
        )
        event.save()
        self.object.status = POTENTIAL_FELLOWSHIP_INVITED
        self.object.save()
        return super().form_valid(form)


class PotentialFellowshipEventCreateView(PermissionsMixin, CreateView):
    """
    Add an event for a Potential Fellowship.
    """

    permission_required = "scipost.can_manage_college_composition"
    form_class = PotentialFellowshipEventForm
    success_url = reverse_lazy("colleges:potential_fellowships")
        form.instance.potfel = get_object_or_404(
            PotentialFellowship, id=self.kwargs["pk"]
        )
        form.instance.noted_on = timezone.now()
        form.instance.noted_by = self.request.user.contributor
        messages.success(self.request, "Event added successfully")
@user_passes_test(is_edadmin_or_advisory_or_active_regular_or_senior_fellow)
def nominations(request):
    """
    List Nominations.
    """
    return render(request, "colleges/nominations.html", context)
@user_passes_test(is_edadmin_or_advisory_or_active_regular_or_senior_fellow)
def _hx_nomination_form(request, profile_id):
    profile = get_object_or_404(Profile, pk=profile_id)
    failed_eligibility_criteria = check_profile_eligibility_for_fellowship(profile)
    if failed_eligibility_criteria:
        return render(
            request,
            "colleges/_hx_failed_eligibility_criteria.html",
                "profile": profile,
                "failed_eligibility_criteria": failed_eligibility_criteria,
            },
    nomination_form = FellowshipNominationForm(request.POST or None, profile=profile)
    if nomination_form.is_valid():
        nomination = nomination_form.save()
        nomination.add_event(description="Nominated", by=request.user.contributor)
        return HTMXResponse(
            f"{nomination.profile} successfully nominated to {nomination.college}.",
            tag="success",
        )
    nomination_form.fields["nominated_by"].initial = request.user.contributor
        "profile": profile,
        "nomination_form": nomination_form,
    return render(request, "colleges/_hx_nomination_form.html", context)
def _hx_nomination_round_remove_voter(request, round_id, voter_id):
    """Remove a voter from a nomination's voting round."""
    round = get_object_or_404(FellowshipNominationVotingRound, pk=round_id)

    voter = get_object_or_404(Fellowship, pk=voter_id)
    if voter in round.eligible_to_vote.all():
        round.eligible_to_vote.remove(voter)
        round.save()
        messages.success(
            request, f"Removed {voter} from the voters list of this round."
        )
    else:
        messages.error(request, f"{voter} was not in the voters list of this round.")
    return HttpResponse("")


@user_passes_test(is_edadmin_or_advisory_or_active_regular_or_senior_fellow)
def _hx_nomination_details_contents(request, nomination_id):
    """For (re)loading the details if modified."""
    nomination = get_object_or_404(FellowshipNomination, pk=nomination_id)
    fellow = request.user.contributor.session_fellowship(request)
    start_email_sent = nomination.events.filter(
        description__contains="start email sent",
    ).exists()
Jean-Sébastien Caux's avatar
Jean-Sébastien Caux committed
    context = {
        "nomination": nomination,
        "start_email_sent": start_email_sent,
        "fellow": fellow,
Jean-Sébastien Caux's avatar
Jean-Sébastien Caux committed
    }
    return render(request, "colleges/_hx_nomination_details_contents.html", context)
@login_required
@user_passes_test(is_edadmin_or_advisory_or_active_regular_or_senior_fellow)
def _hx_voting_round_summary(request, round_id):
    """For (re)loading the round summary if modified."""
    round = get_object_or_404(FellowshipNominationVotingRound, pk=round_id)
    context = {
        "round": round,
    }
    return render(request, "colleges/_hx_voting_round_summary.html", context)


def _hx_nominations_search_form(request, filter_set: str):
    form = FellowshipNominationSearchForm(
        user=request.user,
        session_key=request.session.session_key,
    )

    if filter_set == "empty":
        form.apply_filter_set({}, none_on_empty=True)
    return render(request, "colleges/_hx_nominations_search_form.html", context)
def _hx_nominations_list(request):
    form = FellowshipNominationSearchForm(
        request.POST or None, user=request.user, session_key=request.session.session_key
    )
    if form.is_valid():
        nominations = form.search_results()
        nominations = FellowshipNomination.objects.all()
    paginator = Paginator(nominations, 16)
    page_nr = request.GET.get("page")
    page_obj = paginator.get_page(page_nr)
    count = paginator.count
    start_index = page_obj.start_index
    context = {
        "count": count,
        "page_obj": page_obj,
        "start_index": start_index,
    }
    return render(request, "colleges/_hx_nominations_list.html", context)
def _hx_nomination_voting_rounds_tab(request, nomination_id, round_id):
    """Render the selected voting round contents and display the others as tabs."""
    nomination = get_object_or_404(FellowshipNomination, pk=nomination_id)
    voting_rounds = nomination.voting_rounds.all().order_by("-voting_opens")

    inaccessible_round_ids = [
        round.id for round in voting_rounds if not round.can_view(request.user)
    ]

    should_show_new_round_tab_btn = request.user.contributor.is_ed_admin and (
        nomination.voting_rounds.count() == 0
        or (
            nomination.latest_voting_round.is_closed
            and (decision := getattr(nomination.latest_voting_round, "decision", None))
            and not decision.outcome == FellowshipNominationDecision.OUTCOME_ELECTED
        )
    )

        "nomination": nomination,
        "voting_rounds": voting_rounds,
        "inaccessible_round_ids": inaccessible_round_ids,
        "should_show_new_round_tab_btn": should_show_new_round_tab_btn,
    if round_id != 0:
        selected_round = voting_rounds.get(id=round_id)
        context["selected_round"] = selected_round

    return render(request, "colleges/_hx_nomination_voting_rounds_tab.html", context)


@login_required
@user_passes_test(is_edadmin)
def _hx_nomination_voting_rounds_create(request, nomination_id):
    nomination = get_object_or_404(FellowshipNomination, pk=nomination_id)
    new_round = FellowshipNominationVotingRound(
        nomination=nomination, voting_opens=None, voting_deadline=None
    )
    new_round.save()
    _ = _hx_nomination_round_add_eligible_voter_set(
        request, new_round.id, "with_specialty_overlap"
    )

    return _hx_nomination_voting_rounds_tab(request, nomination_id, new_round.id)


@user_passes_test(is_edadmin_or_advisory_or_active_regular_or_senior_fellow)
def _hx_nomination_comments(request, nomination_id):
    nomination = get_object_or_404(FellowshipNomination, pk=nomination_id)
Jean-Sébastien Caux's avatar
Jean-Sébastien Caux committed
    initial = {
        "nomination": nomination,
        "by": request.user.contributor,
    }
    form = FellowshipNominationCommentForm(request.POST or None, initial=initial)
    if form.is_valid():
        form.save()
        nomination.add_event(description="Comment added", by=request.user.contributor)
        form = FellowshipNominationCommentForm(initial=initial)
Jean-Sébastien Caux's avatar
Jean-Sébastien Caux committed
    context = {
        "nomination": nomination,
        "form": form,
    }
    return render(request, "colleges/_hx_nomination_comments.html", context)


@user_passes_test(is_edadmin_or_advisory_or_active_regular_or_senior_fellow)
def _hx_nomination_vote(request, round_id):
    fellowship = request.user.contributor.session_fellowship(request)
    voting_round = get_object_or_404(
        FellowshipNominationVotingRound,
        eligible_to_vote=fellowship,
    )

    # Check if the voting round is still open
    if not voting_round.is_open:
        return HTMXResponse(
            """You cannot vote in non-open rounds.""",
            tag="danger",
        )
    if request.method == "POST":
        vote_object, created = FellowshipNominationVote.objects.update_or_create(
            voting_round=voting_round,
            fellow=fellowship,
            defaults={
                "vote": request.POST.get("vote"),
                "on": timezone.now(),
Jean-Sébastien Caux's avatar
Jean-Sébastien Caux committed
            },
        if created:
            voting_round.nomination.add_event(
                description="Vote received",
                by=request.user.contributor,
            )
            voting_round.nomination.add_event(
                description="Vote updated",
                by=request.user.contributor,
            )
    else:
        vote_object = FellowshipNominationVote.objects.filter(
            voting_round=voting_round,
            fellow=fellowship,
        ).first()
Jean-Sébastien Caux's avatar
Jean-Sébastien Caux committed
    context = {
        "voting_round": voting_round,
        "vote_object": vote_object,
George Katsikas's avatar
George Katsikas committed
        "VOTE_BS_CLASSES": FellowshipNominationVote.VOTE_BS_CLASSES,
Jean-Sébastien Caux's avatar
Jean-Sébastien Caux committed
    }
    return render(request, "colleges/_hx_nomination_vote.html", context)
@login_required
@user_passes_test(is_edadmin_or_advisory_or_active_regular_or_senior_fellow)
def _hx_nomination_veto(request, nomination_id):
    fellow = request.user.contributor.session_fellowship(request)
    nomination = get_object_or_404(FellowshipNomination, pk=nomination_id)

    # Check that vetoer is a sneior fellow is in the same college as the nomination
    if (
        not fellow.contributor.fellowships.filter(college=nomination.college)
        .senior()
        .active()
        .exists()
    ):
        return HTMXResponse(
            """You are not an active senior fellow of this college.""",
            tag="danger",
        )

    form = FellowshipNominationVetoForm(
        request.POST or None, fellow=fellow, nomination=nomination
    )

    if form.is_valid():
        form.save()
        return render(
            request,
            "colleges/_hx_nomination_veto_btn.html",
            {
                "nomination": nomination,
                "fellow": fellow,
            },
        )

    return render(request, "colleges/_hx_nomination_veto.html", {"form": form})


@login_required
@user_passes_test(is_edadmin)
def _hx_voting_round_start_form(request, round_id):
    round = get_object_or_404(FellowshipNominationVotingRound, pk=round_id)
    form = FellowshipNominationVotingRoundStartForm(
        request.POST or None,
        instance=round,
    )
    if form.is_valid():
        form.save()
        messages.success(
            request,
            f"Voting round for {round.nomination.profile} started "
            f"from {round.voting_opens} until {round.voting_deadline}.",