SciPost Code Repository

Skip to content
Snippets Groups Projects
views.py 23.6 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_active_regular_or_senior_fellow
)
from colleges.utils import check_profile_eligibility_for_fellowship
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,
Jorran de Wit's avatar
Jorran de Wit committed
    POTENTIAL_FELLOWSHIP_EVENT_VOTED_ON, POTENTIAL_FELLOWSHIP_EVENT_EMAILED)
from .forms import (
    FellowshipDynSelForm, FellowshipForm,
    FellowshipRemoveSubmissionForm, FellowshipAddSubmissionForm,
    SubmissionAddFellowshipForm,
    FellowshipRemoveProceedingsForm, FellowshipAddProceedingsForm,
    PotentialFellowshipForm, PotentialFellowshipStatusForm, PotentialFellowshipEventForm,
    FellowshipNominationForm, FellowshipNominationSearchForm,
from .models import (
    College, Fellowship,
    PotentialFellowship, PotentialFellowshipEvent,
    FellowshipNominationVotingRound
)
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
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()
        return qs


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.
        """
        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()
        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'
    model = Fellowship


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')})
        return context

@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')


@login_required
@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})
    if form.is_valid():
        form.save()
        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 submission_fellowships(request, identifier_w_vn_nr):
Jorran de Wit's avatar
Jorran de Wit committed
    """
    List all Fellowships related to Submission.
    """
    submission = get_object_or_404(Submission, preprint__identifier_w_vn_nr=identifier_w_vn_nr)
Jorran de Wit's avatar
Jorran de Wit committed

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


@login_required
@user_passes_test(is_edadmin_or_senior_fellow)
def submission_add_fellowship(request, identifier_w_vn_nr):
    """Add Fellowship to a Submission's Fellowship."""
    submission = get_object_or_404(Submission, 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))
        return redirect(reverse('colleges:submission',
                                args=(submission.preprint.identifier_w_vn_nr,)))
Jorran de Wit's avatar
Jorran de Wit committed

    context = {
        'submission': submission,
        'form': form,
    }
    return render(request, 'colleges/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)
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)
Jorran de Wit's avatar
Jorran de Wit committed
    form = FellowshipRemoveSubmissionForm(request.POST or None,
                                          submission=submission, instance=fellowship)

    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)


@login_required
@permission_required('scipost.can_manage_college_composition', raise_exception=True)
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)
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)

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

    context = {
        'fellowship': fellowship,
        'form': form,
        'proceedings': proceedings
    }
    return render(request, 'colleges/fellowship_proceedings_remove.html', context)


@login_required
@permission_required('scipost.can_manage_college_composition', raise_exception=True)
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))
        return redirect(fellowship.get_absolute_url())

    context = {
        'fellowship': fellowship,
        'form': form,
    }
    return render(request, 'colleges/fellowship_proceedings_add.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'
    model = PotentialFellowship
    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_on=timezone.now(),
            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'
    model = PotentialFellowship
    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()
        return context


@login_required
@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)
    if vote == 'A':
        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()
Jean-Sébastien Caux's avatar
Jean-Sébastien Caux committed
    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_on=timezone.now(),
            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')

    def form_valid(self, form):
        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')
        return super().form_valid(form)
@user_passes_test(is_edadmin_or_active_regular_or_senior_fellow)
def nominations(request):
    """
    List Nominations.
    """
    profile_dynsel_form = ProfileDynSelForm(
        initial={
            'action_url_name': 'colleges:_hx_nomination_form',
            'action_url_base_kwargs': { },
            'action_target_element_id': 'nomination_form_response'
        }
    )
        'profile_dynsel_form': profile_dynsel_form,
        'form': FellowshipNominationSearchForm(),
    }
    return render(request, 'colleges/nominations.html', context)


@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)
    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()
        return HttpResponse(
            f'<div class="bg-success text-white p-2 ">{nomination.profile} '
            f'successfully nominated to {nomination.college}.</div>')
    nomination_form.fields['nominated_by'].initial = request.user.contributor
    context = {
        'profile': profile,
        'nomination_form': nomination_form,
    }
    return render(request, 'colleges/_hx_nomination_form.html', context)


@user_passes_test(is_edadmin_or_active_regular_or_senior_fellow)
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)


@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)