diff --git a/scipost_django/colleges/forms.py b/scipost_django/colleges/forms.py index fb9e5acb54d0d1b70f08098679c0cc5332980709..37da59baed691048df9a68a33ddf74c2bc8c1d1f 100644 --- a/scipost_django/colleges/forms.py +++ b/scipost_django/colleges/forms.py @@ -28,6 +28,7 @@ from .constants import ( POTENTIAL_FELLOWSHIP_IDENTIFIED, POTENTIAL_FELLOWSHIP_NOMINATED, POTENTIAL_FELLOWSHIP_EVENT_DEFINED, POTENTIAL_FELLOWSHIP_EVENT_NOMINATED ) +from .utils import check_profile_eligibility_for_fellowship class FellowshipSelectForm(forms.Form): @@ -287,27 +288,18 @@ class PotentialFellowshipEventForm(forms.ModelForm): class FellowshipNominationForm(forms.ModelForm): - # college = forms.ModelChoiceField( - # queryset=College.objects.all(), - # ) - # nominee = forms.ModelChoiceField( - # queryset=Profile.objects.none(), - # #widget=autocomplete.ModelSelect2(url='/profiles/profile-autocomplete'), - # ) - # nominator_comments = forms.CharField( - # required=False, - # widget=forms.Textarea() - # ) - profile_id = forms.IntegerField() + #profile_id = forms.IntegerField() class Meta: model = FellowshipNomination fields = [ - 'profile_id', 'nominated_by', # hidden + #'profile_id', + 'nominated_by', # hidden 'college', 'nominator_comments' # visible ] def __init__(self, *args, **kwargs): + self.profile = kwargs.pop('profile') super().__init__(*args, **kwargs) self.fields['nominator_comments'].widget.attrs['rows'] = 4 self.helper = FormHelper() @@ -325,60 +317,12 @@ class FellowshipNominationForm(forms.ModelForm): ), ) - def clean_profile_id(self): - """ - Requirements: - - - no current Fellowship exists - - no current FellowshipNomination exists - - no 'not elected' decision in last 2 years - - no invitation was turned down in the last 2 years - """ - profile_id = self.cleaned_data['profile_id'] - try: - self.profile = Profile.objects.get(pk=profile_id) - except Profile.DoesNotExist: - self.add_error( - 'profile_id', - 'Profile not found') - if Fellowship.objects.active().regular_or_senior().filter( - contributor__profile=self.profile).exists(): - self.add_error( - 'profile_id', - 'This Profile is associated to an active Fellowship.' - ) - latest_nomination = FellowshipNomination.objects.filter( - profile=self.profile).first() - if latest_nomination: - try: - if (latest_nomination.decision.fixed_on + - datetime.timedelta(days=730)) > timezone.now(): - if latest_nomination.decision.elected: - try: - if latest_nomination.invitation.declined: - self.add_error( - 'profile_id', - 'Invitation declined less that 2 years ago. ' - 'Wait to try again.') - else: - self.add_error( - 'profile_id', - 'Already elected, invitation in process.') - except AttributeError: - self.add_error( - 'profile_id', - 'Already elected, invitation pending.') - self.add_error( - 'profile_id', - 'Election failed less that 2 years ago. Must wait.') - except AttributeError: # no decision yet - self.add_error( - 'profile_id', - 'This Profile is associated to an ongoing Nomination process.') - return profile_id - def clean(self): data = super().clean() + failed_eligibility_criteria = check_profile_eligibility(self.profile) + if failed_eligibility_criteria: + for critetion in failed_eligibility_criteria: + self.add_error(None, criterion) if data['college'].acad_field != self.profile.acad_field: self.add_error( 'college', diff --git a/scipost_django/colleges/templates/colleges/_hx_failed_eligibility_criteria.html b/scipost_django/colleges/templates/colleges/_hx_failed_eligibility_criteria.html new file mode 100644 index 0000000000000000000000000000000000000000..185718efd63c41d4d39ea91157c4e5e95edcb075 --- /dev/null +++ b/scipost_django/colleges/templates/colleges/_hx_failed_eligibility_criteria.html @@ -0,0 +1,8 @@ +<div class="border border-danger text-danger bg-danger bg-opacity-10 mb-2 p-2"> + <p><strong>{{ profile }}</strong> cannot be nominated at this time:</p> + <ul> + {% for criterion in failed_eligibility_criteria %} + <li class="text-danger">{{ criterion }}</li> + {% endfor %} + </ul> +</div> diff --git a/scipost_django/colleges/templates/colleges/_hx_nomination_form.html b/scipost_django/colleges/templates/colleges/_hx_nomination_form.html index 3c8657490700d1be1256b4f10f8e71fab63418e4..02477f4dd3aa86b578b62309092af659e3453fe0 100644 --- a/scipost_django/colleges/templates/colleges/_hx_nomination_form.html +++ b/scipost_django/colleges/templates/colleges/_hx_nomination_form.html @@ -1,5 +1,5 @@ {% load crispy_forms_tags %} -<h3>Nomination to Fellowship: <em>{{ profile }}</em></h3> +<h3>Nomination to Fellowship: <span class="bg-success bg-opacity-25 p-2"><em>{{ profile }}</em></span></h3> <form hx-post="{% url 'colleges:_hx_nomination_form' profile_id=profile.pk %}" hx-target="#nomination_form_response" diff --git a/scipost_django/colleges/templates/colleges/nominations.html b/scipost_django/colleges/templates/colleges/nominations.html index f4c6146758223a49b701f8b9bfac7861cd32fc22..33ee6ee4a1fcd12c5b55a43ee8440a36b4912d0d 100644 --- a/scipost_django/colleges/templates/colleges/nominations.html +++ b/scipost_django/colleges/templates/colleges/nominations.html @@ -25,9 +25,11 @@ <h3>Procedure</h3> <ul> <li>Type your search query in the search form</li> - <li>When you see the name you're looking for in the list of matches, double-click on it</li> + <li>When the name you're looking for appears in the list of matches, double-click on it</li> <li>The nomination form will appear below</li> - <li>Fill it in and submit!</li> + <li>Non-eligibility flags (if any) will appear</li> + <li>If eligible, fill the form in (comments are optional)</li> + <li>Submit! (the vote will be arranged by EdAdmin)</li> </ul> <form hx-post="{% url 'profiles:_hx_profile_dynsel_list' %}" @@ -38,17 +40,23 @@ {% csrf_token %} <div id="profile_dynsel_form">{% crispy profile_dynsel_form %}</div> </form> - <div id="profile_dynsel_results-indicator" class="htmx-indicator p-2"> - <button class="btn btn-warning" type="button" disabled> - <strong>Loading results...</strong> - <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> - </button> - </div> - <div id="nomination_form_response-indicator" class="htmx-indicator p-2"> - <button class="btn btn-warning" type="button" disabled> - <strong>Loading form...</strong> - <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> - </button> + <div class="row"> + <div class="col"> + <div id="nomination_form_response-indicator" class="htmx-indicator"> + <button class="btn btn-sm btn-warning" type="button" disabled> + <strong>Loading form...</strong> + <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> + </button> + </div> + </div> + <div class="col"> + <div id="profile_dynsel_results-indicator" class="htmx-indicator"> + <button class="btn btn-sm btn-warning" type="button" disabled> + <strong>Loading results...</strong> + <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> + </button> + </div> + </div> </div> </div> <div class="col-lg-6"> diff --git a/scipost_django/colleges/utils.py b/scipost_django/colleges/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..a41e3ca20d32bfae9fbabd14941c275c8629dc99 --- /dev/null +++ b/scipost_django/colleges/utils.py @@ -0,0 +1,41 @@ +__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from .models import Fellowship, FellowshipNomination + + +def check_profile_eligibility_for_fellowship(profile): + """ + Returns a list of failed eligibility criteria (if any). + + Requirements: + + - no current Fellowship exists + - no current FellowshipNomination exists + - no 'not elected' decision in last 2 years + - no invitation was turned down in the last 2 years + """ + blocks = [] + if Fellowship.objects.active().regular_or_senior().filter( + contributor__profile=profile).exists(): + blocks.append('This Profile is associated to an active Fellowship.') + latest_nomination = FellowshipNomination.objects.filter( + profile=profile).first() + if latest_nomination: + try: + if (latest_nomination.decision.fixed_on + + datetime.timedelta(days=730)) > timezone.now(): + if latest_nomination.decision.elected: + try: + if latest_nomination.invitation.declined: + blocks.append('Invitation declined less that 2 years ago. ' + 'Wait to try again.') + else: + blocks.append('Already elected, invitation in process.') + except AttributeError: + blocks.append('Already elected, invitation pending.') + blocks.append('Election failed less that 2 years ago. Must wait.') + except AttributeError: # no decision yet + blocks.append('This Profile is associated to an ongoing Nomination process.') + return blocks if len(blocks) > 0 else None diff --git a/scipost_django/colleges/views.py b/scipost_django/colleges/views.py index e9c032567d99b3ef58c3bbcae6fa0ba728433545..1f7a37a7f30530f9c4c4583c196a4ef6241faadd 100644 --- a/scipost_django/colleges/views.py +++ b/scipost_django/colleges/views.py @@ -21,6 +21,7 @@ from django.views.generic.list import ListView 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 from submissions.models import Submission from .constants import ( @@ -554,14 +555,26 @@ def nominations(request): @login_required @user_passes_test(is_edadmin_or_active_regular_or_senior_fellow) def _hx_nomination_form(request, profile_id): - nomination_form = FellowshipNominationForm(request.POST or None) + 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>') - profile = get_object_or_404(Profile, pk=profile_id) - nomination_form.fields['profile_id'].initial = profile.pk nomination_form.fields['nominated_by'].initial = request.user.contributor context = { 'profile': profile,