Skip to content
Snippets Groups Projects
views.py 14.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • from copy import copy
    
    
    from django.contrib.auth.decorators import login_required
    
    from django.contrib import messages
    
    from django.core.exceptions import PermissionDenied
    
    from django.http import HttpResponseRedirect
    
    from django.shortcuts import get_object_or_404
    
    from django.utils.decorators import method_decorator
    
    Todd Dembrey's avatar
    Todd Dembrey committed
    from django.utils.text import mark_safe
    
    from django.utils.translation import ugettext_lazy as _
    
    from django.views.generic import DetailView, ListView, UpdateView
    
    from django_filters.views import FilterView
    from django_tables2.views import SingleTableMixin
    
    
    from opentech.apply.activity.views import (
        AllActivityContextMixin,
        ActivityContextMixin,
        CommentFormView,
        DelegatedViewMixin,
    )
    
    from opentech.apply.activity.messaging import messenger, MESSAGES
    
    from opentech.apply.funds.workflow import DETERMINATION_OUTCOMES
    
    from opentech.apply.review.views import ReviewContextMixin
    
    from opentech.apply.users.decorators import staff_required
    
    from opentech.apply.utils.views import DelegateableView, ViewDispatcher
    
    from .differ import compare
    
    from .forms import ProgressSubmissionForm, UpdateReviewersForm, UpdateSubmissionLeadForm
    
    from .models import ApplicationSubmission, ApplicationRevision
    
    from .tables import AdminSubmissionsTable, SubmissionFilter, SubmissionFilterAndSearch
    
    from .workflow import STAGE_CHANGE_ACTIONS
    
    @method_decorator(staff_required, name='dispatch')
    
    class SubmissionListView(AllActivityContextMixin, SingleTableMixin, FilterView):
    
        template_name = 'funds/submissions.html'
    
        table_class = AdminSubmissionsTable
    
    
        filterset_class = SubmissionFilter
    
    
        def get_queryset(self):
    
            return self.filterset_class._meta.model.objects.current().for_table(self.request.user)
    
        def get_context_data(self, **kwargs):
            active_filters = self.filterset.data
            return super().get_context_data(active_filters=active_filters, **kwargs)
    
    
    
    @method_decorator(staff_required, name='dispatch')
    
    class SubmissionSearchView(SingleTableMixin, FilterView):
        template_name = 'funds/submissions_search.html'
    
        table_class = AdminSubmissionsTable
    
    
        filterset_class = SubmissionFilterAndSearch
    
    
        def get_queryset(self):
    
            return self.filterset_class._meta.model.objects.current().for_table(self.request.user)
    
        def get_context_data(self, **kwargs):
            search_term = self.request.GET.get('query')
    
            # We have more data than just 'query'
            active_filters = len(self.filterset.data) > 1
    
            return super().get_context_data(
                search_term=search_term,
                active_filters=active_filters,
                **kwargs,
            )
    
    
    @method_decorator(staff_required, name='dispatch')
    
    Todd Dembrey's avatar
    Todd Dembrey committed
    class ProgressSubmissionView(DelegatedViewMixin, UpdateView):
    
        model = ApplicationSubmission
        form_class = ProgressSubmissionForm
        context_name = 'progress_form'
    
        def form_valid(self, form):
    
            action = form.cleaned_data.get('action')
            # Defer to the determination form for any of the determination transitions
    
            if action in DETERMINATION_OUTCOMES:
    
    Dan Braghis's avatar
    Dan Braghis committed
                return HttpResponseRedirect(reverse_lazy(
                    'apply:submissions:determinations:form',
                    args=(form.instance.id,)) + "?action=" + action)
    
            self.object.perform_transition(action, self.request.user, request=self.request)
    
            return super().form_valid(form)
    
    @method_decorator(staff_required, name='dispatch')
    
    class UpdateLeadView(DelegatedViewMixin, UpdateView):
        model = ApplicationSubmission
        form_class = UpdateSubmissionLeadForm
        context_name = 'lead_form'
    
        def form_valid(self, form):
    
            # Fetch the old lead from the database
    
            old = copy(self.get_object())
    
            response = super().form_valid(form)
    
            messenger(
                MESSAGES.UPDATE_LEAD,
    
                request=self.request,
    
                user=self.request.user,
    
                submission=form.instance,
    
    @method_decorator(staff_required, name='dispatch')
    class UpdateReviewersView(DelegatedViewMixin, UpdateView):
        model = ApplicationSubmission
        form_class = UpdateReviewersForm
        context_name = 'reviewer_form'
    
        def form_valid(self, form):
    
            old_reviewers = set(self.get_object().reviewers.all())
    
            response = super().form_valid(form)
    
            new_reviewers = set(form.instance.reviewers.all())
    
            added = new_reviewers - old_reviewers
            removed = old_reviewers - new_reviewers
    
            messenger(
                MESSAGES.REVIEWERS_UPDATED,
    
                user=self.request.user,
                submission=self.kwargs['submission'],
    
                added=added,
                removed=removed,
    
    class AdminSubmissionDetailView(ReviewContextMixin, ActivityContextMixin, DelegateableView):
    
        template_name_suffix = '_admin_detail'
    
        model = ApplicationSubmission
    
        form_views = [
            ProgressSubmissionView,
            CommentFormView,
            UpdateLeadView,
    
            UpdateReviewersView,
    
        def dispatch(self, request, *args, **kwargs):
            submission = self.get_object()
    
            redirect = SubmissionSealedView.should_redirect(request, submission)
            return redirect or super().dispatch(request, *args, **kwargs)
    
        def get_context_data(self, **kwargs):
    
            other_submissions = self.model.objects.filter(user=self.object.user).current().exclude(id=self.object.id)
            if self.object.next:
                other_submissions = other_submissions.exclude(id=self.object.next.id)
    
    
            return super().get_context_data(
    
                other_submissions=other_submissions,
    
                **kwargs,
    
    @method_decorator(staff_required, 'dispatch')
    class SubmissionSealedView(DetailView):
        template_name = 'funds/submission_sealed.html'
        model = ApplicationSubmission
    
    
        def get(self, request, *args, **kwargs):
            submission = self.get_object()
            if not self.round_is_sealed(submission):
                return self.redirect_detail(submission)
            return super().get(request, *args, **kwargs)
    
        def post(self, request, *args, **kwargs):
            submission = self.get_object()
            if self.can_view_sealed(request.user):
                self.peeked(submission)
            return self.redirect_detail(submission)
    
        def redirect_detail(self, submission):
            return HttpResponseRedirect(reverse_lazy('funds:submissions:detail', args=(submission.id,)))
    
        def peeked(self, submission):
            messenger(
                MESSAGES.OPENED_SEALED,
                request=self.request,
                user=self.request.user,
                submission=submission,
            )
            self.request.session.setdefault('peeked', {})[str(submission.id)] = True
    
            # Dictionary updates do not trigger session saves. Force update
            self.request.session.modified = True
    
    
        def can_view_sealed(self, user):
            return user.is_superuser
    
        def get_context_data(self, **kwargs):
            return super().get_context_data(
                can_view_sealed=self.can_view_sealed(self.request.user),
                **kwargs,
            )
    
        @classmethod
        def round_is_sealed(cls, submission):
    
            try:
                return submission.round.specific.is_sealed
            except AttributeError:
                # Its a lab - cant be sealed
                return False
    
    
        @classmethod
        def has_peeked(cls, request, submission):
            return str(submission.id) in request.session.get('peeked', {})
    
        @classmethod
        def should_redirect(cls, request, submission):
            if cls.round_is_sealed(submission) and not cls.has_peeked(request, submission):
                return HttpResponseRedirect(reverse_lazy('funds:submissions:sealed', args=(submission.id,)))
    
    
    class ApplicantSubmissionDetailView(ActivityContextMixin, DelegateableView):
    
        model = ApplicationSubmission
    
            return super().get_object().from_draft()
    
        def dispatch(self, request, *args, **kwargs):
            if self.get_object().user != request.user:
                raise PermissionDenied
            return super().dispatch(request, *args, **kwargs)
    
    
    class SubmissionDetailView(ViewDispatcher):
        admin_view = AdminSubmissionDetailView
        applicant_view = ApplicantSubmissionDetailView
    
    
        def admin_check(self, request):
    
            if request.user.is_reviewer:
    
                return True
            return super().admin_check(request)
    
    
    class BaseSubmissionEditView(UpdateView):
    
        """
        Converts the data held on the submission into an editable format and knows how to save
        that back to the object. Shortcuts the normal update view save approach
        """
    
        model = ApplicationSubmission
    
    
        def dispatch(self, request, *args, **kwargs):
    
            if not self.get_object().phase.permissions.can_edit(request.user):
    
            return super().dispatch(request, *args, **kwargs)
    
    
            yield ('save', 'white', 'Save Draft')
    
            yield ('submit', 'primary', 'Submit')
    
        def get_form_kwargs(self):
            kwargs = super().get_form_kwargs()
    
            instance = kwargs.pop('instance').from_draft()
    
            kwargs['initial'] = instance.raw_data
    
        def get_context_data(self, **kwargs):
            return super().get_context_data(buttons=self.buttons(), **kwargs)
    
    
        def get_form_class(self):
            return self.object.get_form_class()
    
    
    
    @method_decorator(staff_required, name='dispatch')
    class AdminSubmissionEditView(BaseSubmissionEditView):
    
        def form_valid(self, form):
    
            self.object.new_data(form.cleaned_data)
    
                self.object.create_revision(draft=True, by=self.request.user)
    
            if 'submit' in self.request.POST:
    
    Todd Dembrey's avatar
    Todd Dembrey committed
                revision = self.object.create_revision(by=self.request.user)
                if revision:
    
                    messenger(
                        MESSAGES.EDIT,
                        request=self.request,
                        user=self.request.user,
                        submission=self.object,
    
    Todd Dembrey's avatar
    Todd Dembrey committed
                        related=revision,
    
            return HttpResponseRedirect(self.get_success_url())
    
    
    @method_decorator(login_required, name='dispatch')
    class ApplicantSubmissionEditView(BaseSubmissionEditView):
        def dispatch(self, request, *args, **kwargs):
            if request.user != self.get_object().user:
                raise PermissionDenied
            return super().dispatch(request, *args, **kwargs)
    
        @property
        def transitions(self):
            transitions = self.object.get_available_user_status_transitions(self.request.user)
            return {
                transition.name: transition
                for transition in transitions
            }
    
        def form_valid(self, form):
            self.object.new_data(form.cleaned_data)
    
            if 'save' in self.request.POST:
                self.object.create_revision(draft=True, by=self.request.user)
    
                messages.success(self.request, _('Submission saved successfully'))
    
                return self.form_invalid(form)
    
    
    Todd Dembrey's avatar
    Todd Dembrey committed
            revision = self.object.create_revision(by=self.request.user)
    
            submitting_proposal = self.object.phase.name in STAGE_CHANGE_ACTIONS
    
            if submitting_proposal:
                messenger(
                    MESSAGES.PROPOSAL_SUBMITTED,
                    request=self.request,
                    user=self.request.user,
    
                    submission=self.object,
    
    Todd Dembrey's avatar
    Todd Dembrey committed
            elif revision:
    
                messenger(
                    MESSAGES.APPLICANT_EDIT,
                    request=self.request,
                    user=self.request.user,
                    submission=self.object,
    
    Todd Dembrey's avatar
    Todd Dembrey committed
                    related=revision,
    
            action = set(self.request.POST.keys()) & set(self.transitions.keys())
    
            transition = self.transitions[action.pop()]
    
    
            self.object.perform_transition(
                transition.target,
                self.request.user,
                request=self.request,
    
    Todd Dembrey's avatar
    Todd Dembrey committed
                notify=not (revision or submitting_proposal),  # Use the other notification
    
            return HttpResponseRedirect(self.get_success_url())
    
    class SubmissionEditView(ViewDispatcher):
        admin_view = AdminSubmissionEditView
        applicant_view = ApplicantSubmissionEditView
    
    
    
    @method_decorator(staff_required, name='dispatch')
    class RevisionListView(ListView):
    
        model = ApplicationRevision
    
        def get_queryset(self):
    
            self.submission = get_object_or_404(ApplicationSubmission, id=self.kwargs['submission_pk'])
    
            self.queryset = self.model.objects.filter(
                submission=self.submission,
            ).exclude(
                draft__isnull=False,
                live__isnull=True,
            )
    
            return super().get_queryset()
    
    
        def get_context_data(self, **kwargs):
            return super().get_context_data(
    
                submission=self.submission,
    
    @method_decorator(staff_required, name='dispatch')
    
    class RevisionCompareView(DetailView):
        model = ApplicationSubmission
    
        template_name = 'funds/revisions_compare.html'
    
        pk_url_kwarg = 'submission_pk'
    
        def compare_revisions(self, from_data, to_data):
    
            self.object.form_data = from_data.form_data
    
            from_fields = self.object.render_answers()
    
            from_required = self.render_required()
    
    
            self.object.form_data = to_data.form_data
    
            to_fields = self.object.render_answers()
    
            to_required = self.render_required()
    
            # Compare all the required fields
            diffed_required = [
                compare(*fields, should_bleach=False)
                for fields in zip(from_required, to_required)
            ]
            for field, diff in zip(self.object.must_include, diffed_required):
                setattr(self.object, 'get_{}_display'.format(field), diff)
    
            diffed_answers = [
    
                compare(*fields, should_bleach=False)
    
                for fields in zip(from_fields, to_fields)
    
            self.object.output_answers = mark_safe(''.join(diffed_answers))
    
        def render_required(self):
            return [
                getattr(self.object, 'get_{}_display'.format(field))()
                for field in self.object.must_include
            ]
    
    
        def get_context_data(self, **kwargs):
    
            from_revision = self.object.revisions.get(id=self.kwargs['from'])
            to_revision = self.object.revisions.get(id=self.kwargs['to'])
    
            self.compare_revisions(from_revision, to_revision)
    
            return super().get_context_data(**kwargs)