diff --git a/opentech/apply/activity/messaging.py b/opentech/apply/activity/messaging.py index faaa386f36438b4c9f97e6975a153fb65f46c644..084dc735497bf38978988518acc5b736eb2bda13 100644 --- a/opentech/apply/activity/messaging.py +++ b/opentech/apply/activity/messaging.py @@ -202,6 +202,7 @@ class ActivityAdapter(AdapterBase): MESSAGES.EDIT: 'Edited', MESSAGES.APPLICANT_EDIT: 'Edited', MESSAGES.UPDATE_LEAD: 'Lead changed from {old_lead} to {submission.lead}', + MESSAGES.BATCH_UPDATE_LEAD: 'Batch Lead changed to {new_lead}', MESSAGES.DETERMINATION_OUTCOME: 'Sent a determination. Outcome: {determination.clean_outcome}', MESSAGES.BATCH_DETERMINATION_OUTCOME: 'batch_determination', MESSAGES.INVITED_TO_PROPOSAL: 'Invited to submit a proposal', @@ -348,6 +349,7 @@ class SlackAdapter(AdapterBase): messages = { MESSAGES.NEW_SUBMISSION: 'A new submission has been submitted for {submission.page.title}: <{link}|{submission.title}>', MESSAGES.UPDATE_LEAD: 'The lead of <{link}|{submission.title}> has been updated from {old_lead} to {submission.lead} by {user}', + MESSAGES.BATCH_UPDATE_LEAD: 'handle_batch_lead', MESSAGES.COMMENT: 'A new {comment.visibility} comment has been posted on <{link}|{submission.title}> by {user}', MESSAGES.EDIT: '{user} has edited <{link}|{submission.title}>', MESSAGES.APPLICANT_EDIT: '{user} has edited <{link}|{submission.title}>', @@ -427,6 +429,16 @@ class SlackAdapter(AdapterBase): return ' '.join(message) + def handle_batch_lead(self, submissions, links, user, new_lead, **kwargs): + submissions_text = self.slack_links(links, submissions) + return ( + '{user} has batch changed lead to {new_lead} on: {submissions_text}'.format( + user=user, + submissions_text=submissions_text, + new_lead=new_lead, + ) + ) + def handle_batch_reviewers(self, submissions, links, user, added, **kwargs): submissions_text = self.slack_links(links, submissions) reviewers_text = ' '.join([ diff --git a/opentech/apply/activity/migrations/0024_add_batch_lead_event.py b/opentech/apply/activity/migrations/0024_add_batch_lead_event.py new file mode 100644 index 0000000000000000000000000000000000000000..88bcf29b814453cda86c498d9df43c7518435c9e --- /dev/null +++ b/opentech/apply/activity/migrations/0024_add_batch_lead_event.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.13 on 2019-07-24 08:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('activity', '0023_notify_partners'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='type', + field=models.CharField(choices=[('UPDATE_LEAD', 'Update Lead'), ('BATCH_UPDATE_LEAD', 'Batch Update Lead'), ('EDIT', 'Edit'), ('APPLICANT_EDIT', 'Applicant Edit'), ('NEW_SUBMISSION', 'New Submission'), ('SCREENING', 'Screening'), ('TRANSITION', 'Transition'), ('BATCH_TRANSITION', 'Batch Transition'), ('DETERMINATION_OUTCOME', 'Determination Outcome'), ('BATCH_DETERMINATION_OUTCOME', 'Batch Determination Outcome'), ('INVITED_TO_PROPOSAL', 'Invited To Proposal'), ('REVIEWERS_UPDATED', 'Reviewers Updated'), ('BATCH_REVIEWERS_UPDATED', 'Batch Reviewers Updated'), ('PARTNERS_UPDATED', 'Partners Updated'), ('PARTNERS_UPDATED_PARTNER', 'Partners Updated Partner'), ('READY_FOR_REVIEW', 'Ready For Review'), ('BATCH_READY_FOR_REVIEW', 'Batch Ready For Review'), ('NEW_REVIEW', 'New Review'), ('COMMENT', 'Comment'), ('PROPOSAL_SUBMITTED', 'Proposal Submitted'), ('OPENED_SEALED', 'Opened Sealed Submission'), ('REVIEW_OPINION', 'Review Opinion'), ('DELETE_SUBMISSION', 'Delete Submission'), ('DELETE_REVIEW', 'Delete Review')], max_length=50), + ), + ] diff --git a/opentech/apply/activity/options.py b/opentech/apply/activity/options.py index c007308a20780b6335827fb159f4152977d3a629..6e9a914cf5025a84da0b72d3e2cb54993b57f3f5 100644 --- a/opentech/apply/activity/options.py +++ b/opentech/apply/activity/options.py @@ -3,6 +3,7 @@ from enum import Enum class MESSAGES(Enum): UPDATE_LEAD = 'Update Lead' + BATCH_UPDATE_LEAD = 'Batch Update Lead' EDIT = 'Edit' APPLICANT_EDIT = "Applicant Edit" NEW_SUBMISSION = 'New Submission' diff --git a/opentech/apply/funds/forms.py b/opentech/apply/funds/forms.py index 4057a2906143566e572b6e1acb8497137eb6d51b..2e80c64be5aff4b0aa7d489b45373f2939d49734 100644 --- a/opentech/apply/funds/forms.py +++ b/opentech/apply/funds/forms.py @@ -77,6 +77,41 @@ class UpdateSubmissionLeadForm(forms.ModelForm): lead_field.queryset = lead_field.queryset.exclude(id=self.instance.lead.id) +class BatchUpdateSubmissionLeadForm(forms.Form): + lead = forms.ChoiceField(label='Lead') + 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) + self.fields['lead'].choices = [(staff.id, staff) for staff in User.objects.staff()] + + def clean_lead(self): + value = self.cleaned_data['lead'] + return User.objects.get(id=value) + + 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 save(self): + new_lead = self.cleaned_data['lead'] + import logging + logger = logging.getLogger('opentech') + logger.debug(new_lead) + logger.debug(new_lead.id) + submissions = self.cleaned_data['submissions'] + + for submission in submissions: + # Onle save if the lead has changed. + if submission.lead != new_lead: + submission.lead = new_lead + submission.save() + + return None + + class UpdateReviewersForm(forms.ModelForm): reviewer_reviewers = forms.ModelMultipleChoiceField( queryset=User.objects.reviewers().only('pk', 'full_name'), diff --git a/opentech/apply/funds/templates/funds/includes/batch_update_lead_form.html b/opentech/apply/funds/templates/funds/includes/batch_update_lead_form.html new file mode 100644 index 0000000000000000000000000000000000000000..a532eccc8d0c702233397c5ed6ae31d96d68af17 --- /dev/null +++ b/opentech/apply/funds/templates/funds/includes/batch_update_lead_form.html @@ -0,0 +1,11 @@ +<div class="modal" id="batch-update-lead"> + <h4 class="modal__header-bar modal__header-bar--no-bottom-space">Update Lead</h4> + <div class="list-reveal"> + <div class="list-reveal__item list-reveal__item--meta" aria-live="polite"> + <span class="js-batch-title-count"></span> + <a href="#" class="list-reveal__link js-toggle-batch-list">Show</a> + </div> + <div class="list-reveal__list js-batch-titles is-closed" aria-live="polite"></div> + </div> + {% include 'funds/includes/delegated_form_base.html' with form=batch_lead_form value='Update'%} +</div> diff --git a/opentech/apply/funds/templates/funds/includes/table_filter_and_search.html b/opentech/apply/funds/templates/funds/includes/table_filter_and_search.html index c4f6ae34922a035333479aec84408dbeee84483a..6f681f328a0e65087a31d6a1f0d73d25e1635907 100644 --- a/opentech/apply/funds/templates/funds/includes/table_filter_and_search.html +++ b/opentech/apply/funds/templates/funds/includes/table_filter_and_search.html @@ -22,6 +22,11 @@ Status </button> + <button data-fancybox data-src="#batch-update-lead" class="button button--action js-batch-button" type="button"> + <svg><use xlink:href="#add-person"></use></svg> + Lead + </button> + <button data-fancybox data-src="#batch-update-reviewers" class="button button--action js-batch-button" type="button"> <svg><use xlink:href="#add-person"></use></svg> Reviewers @@ -64,5 +69,6 @@ </form> </div> +{% include "funds/includes/batch_update_lead_form.html" %} {% include "funds/includes/batch_update_reviewer_form.html" %} {% include "funds/includes/batch_progress_form.html" %} diff --git a/opentech/apply/funds/views.py b/opentech/apply/funds/views.py index 0b05d75483ffeb19edfad866c72088a80c657f07..41e29fc3280f204443617bbdcc7f2b0c5a2f27f0 100644 --- a/opentech/apply/funds/views.py +++ b/opentech/apply/funds/views.py @@ -37,6 +37,7 @@ from opentech.apply.utils.views import DelegateableListView, DelegateableView, V from .differ import compare from .forms import ( + BatchUpdateSubmissionLeadForm, BatchUpdateReviewersForm, BatchProgressSubmissionForm, ProgressSubmissionForm, @@ -102,6 +103,30 @@ class BaseAdminSubmissionsTable(SingleTableMixin, FilterView): ) +@method_decorator(staff_required, name='dispatch') +class BatchUpdateLeadView(DelegatedViewMixin, FormView): + form_class = BatchUpdateSubmissionLeadForm + context_name = 'batch_lead_form' + + def form_valid(self, form): + new_lead = form.cleaned_data['lead'] + submissions = form.cleaned_data['submissions'] + form.save() + + messenger( + MESSAGES.BATCH_UPDATE_LEAD, + request=self.request, + user=self.request.user, + submissions=submissions, + new_lead=new_lead, + ) + return super().form_valid(form) + + def form_invalid(self, form): + messages.error(self.request, mark_safe(_('Sorry something went wrong') + form.errors.as_ul())) + return super().form_invalid(form) + + @method_decorator(staff_required, name='dispatch') class BatchUpdateReviewersView(DelegatedViewMixin, FormView): form_class = BatchUpdateReviewersForm @@ -251,6 +276,7 @@ class SubmissionOverviewView(AllActivityContextMixin, BaseAdminSubmissionsTable) class SubmissionAdminListView(AllActivityContextMixin, BaseAdminSubmissionsTable, DelegateableListView): template_name = 'funds/submissions.html' form_views = [ + BatchUpdateLeadView, BatchUpdateReviewersView, BatchProgressSubmissionView, ] @@ -269,6 +295,7 @@ class SubmissionListView(ViewDispatcher): class SubmissionsByRound(AllActivityContextMixin, BaseAdminSubmissionsTable, DelegateableListView): template_name = 'funds/submissions_by_round.html' form_views = [ + BatchUpdateLeadView, BatchUpdateReviewersView, BatchProgressSubmissionView, ] @@ -300,6 +327,7 @@ class SubmissionsByStatus(BaseAdminSubmissionsTable, DelegateableListView): template_name = 'funds/submissions_by_status.html' status_mapping = PHASES_MAPPING form_views = [ + BatchUpdateLeadView, BatchUpdateReviewersView, BatchProgressSubmissionView, ]