from django import forms from django.utils.text import mark_safe, slugify from django.utils.translation import ugettext_lazy as _ from django_select2.forms import Select2Widget from opentech.apply.users.models import User from .models import AssignedReviewers, ApplicationSubmission, ReviewerRole from .utils import render_icon from .widgets import Select2MultiCheckboxesWidget from .workflow import get_action_mapping class ProgressSubmissionForm(forms.ModelForm): action = forms.ChoiceField(label='Take action') class Meta: model = ApplicationSubmission fields: list = [] def __init__(self, *args, **kwargs): self.user = kwargs.pop('user') super().__init__(*args, **kwargs) choices = list(self.instance.get_actions_for_user(self.user)) action_field = self.fields['action'] action_field.choices = choices self.should_show = bool(choices) class BatchProgressSubmissionForm(forms.Form): action = forms.ChoiceField(label='Take action') submissions = forms.CharField(widget=forms.HiddenInput(attrs={'class': 'js-submissions-id'})) def __init__(self, *args, round=None, **kwargs): self.user = kwargs.pop('user') super().__init__(*args, **kwargs) workflow = round and round.workflow self.action_mapping = get_action_mapping(workflow) choices = [(action, detail['display']) for action, detail in self.action_mapping.items()] self.fields['action'].choices = choices def clean_submissions(self): value = self.cleaned_data['submissions'] submission_ids = [int(submission) for submission in value.split(',')] return ApplicationSubmission.objects.filter(id__in=submission_ids) def clean_action(self): value = self.cleaned_data['action'] action = self.action_mapping[value]['transitions'] return action class ScreeningSubmissionForm(forms.ModelForm): class Meta: model = ApplicationSubmission fields = ('screening_status',) def __init__(self, *args, **kwargs): self.user = kwargs.pop('user') super().__init__(*args, **kwargs) self.should_show = False if self.user.is_apply_staff: self.should_show = True class UpdateSubmissionLeadForm(forms.ModelForm): class Meta: model = ApplicationSubmission fields = ('lead',) def __init__(self, *args, **kwargs): kwargs.pop('user') super().__init__(*args, **kwargs) lead_field = self.fields['lead'] lead_field.label = f'Update lead from { self.instance.lead } to' lead_field.queryset = lead_field.queryset.exclude(id=self.instance.lead.id) class UpdateReviewersForm(forms.ModelForm): reviewer_reviewers = forms.ModelMultipleChoiceField( queryset=User.objects.reviewers().only('pk', 'full_name'), widget=Select2MultiCheckboxesWidget(attrs={'data-placeholder': 'Reviewers'}), label='Reviewers', required=False, ) class Meta: model = ApplicationSubmission fields: list = [] def __init__(self, *args, **kwargs): self.user = kwargs.pop('user') super().__init__(*args, **kwargs) assigned_roles = { assigned.role: assigned.reviewer for assigned in self.instance.assigned.filter( role__isnull=False ) } self.role_fields = {} field_data = make_role_reviewer_fields() for data in field_data: field_name = data['field_name'] self.fields[field_name] = data['field'] self.role_fields[field_name] = data['role'] self.fields[field_name].initial = assigned_roles.get(data['role']) submitted_reviewers = User.objects.filter( id__in=self.instance.assigned.reviewed().values('reviewer'), ) if self.can_alter_external_reviewers(self.instance, self.user): reviewers = self.instance.reviewers.all().only('pk') self.prepare_field( 'reviewer_reviewers', initial=reviewers, excluded=submitted_reviewers ) # Move the non-role reviewers field to the end of the field list self.fields.move_to_end('reviewer_reviewers') else: self.fields.pop('reviewer_reviewers') def prepare_field(self, field_name, initial, excluded): field = self.fields[field_name] field.queryset = field.queryset.exclude(id__in=excluded) field.initial = initial def can_alter_external_reviewers(self, instance, user): return instance.stage.has_external_review and (user == instance.lead or user.is_superuser) def clean(self): cleaned_data = super().clean() role_reviewers = [ user for field, user in self.cleaned_data.items() if field in self.role_fields ] # If any of the users match and are set to multiple roles, throw an error if len(role_reviewers) != len(set(role_reviewers)) and any(role_reviewers): self.add_error(None, _('Users cannot be assigned to multiple roles.')) return cleaned_data def save(self, *args, **kwargs): instance = super().save(*args, **kwargs) """ 1. Update role reviewers 2. Update non-role reviewers 2a. Remove those not on form 2b. Add in any new non-role reviewers selected """ # 1. Update role reviewers assigned_roles = { role: self.cleaned_data[field] for field, role in self.role_fields.items() } for role, reviewer in assigned_roles.items(): if reviewer: AssignedReviewers.objects.update_role(role, reviewer, instance) # 2. Update non-role reviewers # 2a. Remove those not on form if self.can_alter_external_reviewers(self.instance, self.user): reviewers = self.cleaned_data.get('reviewer_reviewers') assigned_reviewers = instance.assigned.without_roles() assigned_reviewers.never_tried_to_review().exclude( reviewer__in=reviewers ).delete() remaining_reviewers = assigned_reviewers.values_list('reviewer_id', flat=True) # 2b. Add in any new non-role reviewers selected AssignedReviewers.objects.bulk_create_reviewers( [reviewer for reviewer in reviewers if reviewer.id not in remaining_reviewers], instance, ) return instance class BatchUpdateReviewersForm(forms.Form): submissions = forms.CharField(widget=forms.HiddenInput(attrs={'class': 'js-submissions-id'})) def __init__(self, *args, user=None, round=None, **kwargs): super().__init__(*args, **kwargs) self.role_fields = {} field_data = make_role_reviewer_fields() for data in field_data: field_name = data['field_name'] self.fields[field_name] = data['field'] self.role_fields[field_name] = data['role'] def clean_submissions(self): value = self.cleaned_data['submissions'] submission_ids = [int(submission) for submission in value.split(',')] return ApplicationSubmission.objects.filter(id__in=submission_ids) def clean(self): cleaned_data = super().clean() role_reviewers = [ user for field, user in self.cleaned_data.items() if field in self.role_fields ] # If any of the users match and are set to multiple roles, throw an error if len(role_reviewers) != len(set(role_reviewers)) and any(role_reviewers): self.add_error(None, _('Users cannot be assigned to multiple roles.')) return cleaned_data def save(self): submissions = self.cleaned_data['submissions'] assigned_roles = { role: self.cleaned_data[field] for field, role in self.role_fields.items() } for role, reviewer in assigned_roles.items(): if reviewer: AssignedReviewers.objects.update_role(role, reviewer, *submissions) return None def make_role_reviewer_fields(): role_fields = [] staff_reviewers = User.objects.staff().only('full_name', 'pk') for role in ReviewerRole.objects.all().order_by('order'): field_name = 'role_reviewer_' + slugify(str(role)) field = forms.ModelChoiceField( queryset=staff_reviewers, widget=Select2Widget(attrs={ 'data-placeholder': 'Select a reviewer', }), required=False, label=mark_safe(render_icon(role.icon) + f'{role.name} Reviewer'), ) role_fields.append({ 'role': role, 'field': field, 'field_name': field_name, }) return role_fields class UpdatePartnersForm(forms.ModelForm): partner_reviewers = forms.ModelMultipleChoiceField( queryset=User.objects.partners(), widget=Select2MultiCheckboxesWidget(attrs={'data-placeholder': 'Partners'}), label='Partners', required=False, ) class Meta: model = ApplicationSubmission fields: list = [] def __init__(self, *args, **kwargs): kwargs.pop('user') super().__init__(*args, **kwargs) partners = self.instance.partners.all() self.submitted_partners = User.objects.partners().filter(id__in=self.instance.reviews.values('author')) partner_field = self.fields['partner_reviewers'] partner_field.queryset = partner_field.queryset.exclude(id__in=self.submitted_partners) partner_field.initial = partners def save(self, *args, **kwargs): instance = super().save(*args, **kwargs) instance.partners.set( self.cleaned_data['partner_reviewers'] | self.submitted_partners ) return instance