Skip to content
Snippets Groups Projects
views.py 31.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • from copy import copy
    
    from wsgiref.util import FileWrapper
    
    from django.conf import settings
    
    Parbhat Puri's avatar
    Parbhat Puri committed
    from django.contrib.auth.decorators import login_required, permission_required
    
    from django.contrib.auth.mixins import UserPassesTestMixin
    from django.contrib.auth.views import redirect_to_login
    
    from django.contrib import messages
    
    from django.core.exceptions import PermissionDenied
    
    from django.core.files.storage import get_storage_class
    
    from django.db.models import Count, F, Q
    
    from django.http import HttpResponseRedirect, Http404, StreamingHttpResponse
    
    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, FormView, ListView, UpdateView, DeleteView, View
    
    from django_filters.views import FilterView
    from django_tables2.views import SingleTableMixin
    
    
    from wagtail.core.models import Page
    
    
    from opentech.apply.activity.views import (
        AllActivityContextMixin,
        ActivityContextMixin,
        CommentFormView,
        DelegatedViewMixin,
    )
    
    from opentech.apply.activity.messaging import messenger, MESSAGES
    
    from opentech.apply.determinations.views import BatchDeterminationCreateView, DeterminationCreateOrUpdateView
    
    from opentech.apply.review.views import ReviewContextMixin
    
    from opentech.apply.users.decorators import staff_required
    
    from opentech.apply.utils.views import DelegateableListView, DelegateableView, ViewDispatcher
    
    from .differ import compare
    
    from .forms import (
        BatchUpdateReviewersForm,
    
        BatchProgressSubmissionForm,
    
        ProgressSubmissionForm,
        ScreeningSubmissionForm,
        UpdateReviewersForm,
        UpdateSubmissionLeadForm,
    
        UpdatePartnersForm,
    
    from .models import (
        ApplicationSubmission,
        ApplicationRevision,
        RoundsAndLabs,
        RoundBase,
        LabBase
    )
    
    from .tables import (
        AdminSubmissionsTable,
    
        RoundsTable,
        RoundsFilter,
        SubmissionFilterAndSearch,
    
        SubmissionReviewerFilterAndSearch,
    
        SummarySubmissionsTable,
    
    from .workflow import INITIAL_STATE, STAGE_CHANGE_ACTIONS, PHASES_MAPPING, review_statuses
    
    from .permissions import is_user_has_access_to_view_submission
    
    submission_storage = get_storage_class(getattr(settings, 'PRIVATE_FILE_STORAGE', None))()
    
    class BaseAdminSubmissionsTable(SingleTableMixin, FilterView):
    
        table_class = AdminSubmissionsTable
    
        filterset_class = SubmissionFilterAndSearch
    
        excluded_fields = []
    
        @property
        def excluded(self):
            return {
                'exclude': self.excluded_fields
            }
    
    
        def get_table_kwargs(self, **kwargs):
            return {**self.excluded, **kwargs}
    
        def get_filterset_kwargs(self, filterset_class, **kwargs):
            new_kwargs = super().get_filterset_kwargs(filterset_class)
            new_kwargs.update(self.excluded)
            new_kwargs.update(kwargs)
            return new_kwargs
    
        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')
    
                search_term=search_term,
    
                filter_action=self.filter_action,
    
    Erin Mullaney's avatar
    Erin Mullaney committed
                **kwargs,
    
    @method_decorator(staff_required, name='dispatch')
    
    class BatchUpdateReviewersView(DelegatedViewMixin, FormView):
    
        form_class = BatchUpdateReviewersForm
        context_name = 'batch_reviewer_form'
    
    
        def form_valid(self, form):
    
            submissions = form.cleaned_data['submissions']
    
            reviewers = [
                [role, form.cleaned_data[field_name]]
                for field_name, role in form.role_fields.items()
            ]
    
            messenger(
                MESSAGES.BATCH_REVIEWERS_UPDATED,
                request=self.request,
                user=self.request.user,
                submissions=submissions,
                added=reviewers,
    
            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 BatchProgressSubmissionView(DelegatedViewMixin, FormView):
        form_class = BatchProgressSubmissionForm
        context_name = 'batch_progress_form'
    
        def form_valid(self, form):
    
            submissions = form.cleaned_data['submissions']
    
            transitions = form.cleaned_data.get('action')
    
            try:
                redirect = BatchDeterminationCreateView.should_redirect(self.request, submissions, transitions)
    
            except ValueError as e:
                messages.warning(self.request, 'Could not determine: ' + str(e))
    
                return self.form_invalid(form)
            else:
                if redirect:
                    return redirect
    
    
            phase_changes = {}
    
            for submission in submissions:
    
                valid_actions = {action for action, _ in submission.get_actions_for_user(self.request.user)}
    
                old_phase = submission.phase
    
                    transition = (valid_actions & set(transitions)).pop()
                    submission.perform_transition(
                        transition,
                        self.request.user,
                        request=self.request,
                        notify=False,
                    )
                except (PermissionDenied, KeyError):
    
                    failed.append(submission)
    
                    phase_changes[submission.id] = old_phase
    
    
            if failed:
                messages.warning(
                    self.request,
    
                    _('Failed to update: ') +
    
                    ', '.join(str(submission) for submission in failed)
                )
    
            succeeded_submissions = submissions.exclude(id__in=[submission.id for submission in failed])
    
            messenger(
                MESSAGES.BATCH_TRANSITION,
                user=self.request.user,
                request=self.request,
    
                submissions=succeeded_submissions,
    
                related=phase_changes,
            )
    
    
            ready_for_review = [
                phase for phase in transitions
                if phase in review_statuses
            ]
            if ready_for_review:
                messenger(
                    MESSAGES.BATCH_READY_FOR_REVIEW,
                    user=self.request.user,
                    request=self.request,
                    submissions=succeeded_submissions.filter(status__in=ready_for_review),
                )
    
    
            return super().form_valid(form)
    
    
    
    class BaseReviewerSubmissionsTable(BaseAdminSubmissionsTable):
        table_class = ReviewerSubmissionsTable
        filterset_class = SubmissionReviewerFilterAndSearch
    
        def get_queryset(self):
            # Reviewers can only see submissions they have reviewed
            return super().get_queryset().reviewed_by(self.request.user)
    
    
    
    @method_decorator(staff_required, name='dispatch')
    
    class SubmissionOverviewView(AllActivityContextMixin, BaseAdminSubmissionsTable):
        template_name = 'funds/submissions_overview.html'
    
        table_class = SummarySubmissionsTable
    
        table_pagination = False
    
        filter_action = reverse_lazy('funds:submissions:list')
    
        def get_table_data(self):
            return super().get_table_data().order_by(F('last_update').desc(nulls_last=True))[:5]
    
        def get_context_data(self, **kwargs):
    
    Erin Mullaney's avatar
    Erin Mullaney committed
            base_query = RoundsAndLabs.objects.with_progress().active().order_by('-end_date')
            open_rounds = base_query.open()[:6]
            open_query = '?round_state=open'
            closed_rounds = base_query.closed()[:6]
            closed_query = '?round_state=closed'
            rounds_title = 'All Rounds and Labs'
    
    
            status_counts = dict(
                ApplicationSubmission.objects.current().values('status').annotate(
                    count=Count('status'),
                ).values_list('status', 'count')
            )
    
            grouped_statuses = {
                status: {
                    'name': data['name'],
                    'count': sum(status_counts.get(status, 0) for status in data['statuses']),
                }
                for status, data in PHASES_MAPPING.items()
            }
    
    
    Erin Mullaney's avatar
    Erin Mullaney committed
            return super().get_context_data(
                open_rounds=open_rounds,
                open_query=open_query,
                closed_rounds=closed_rounds,
                closed_query=closed_query,
                rounds_title=rounds_title,
    
                status_counts=grouped_statuses,
    
    Erin Mullaney's avatar
    Erin Mullaney committed
                **kwargs,
            )
    
    class SubmissionAdminListView(AllActivityContextMixin, BaseAdminSubmissionsTable, DelegateableListView):
    
        template_name = 'funds/submissions.html'
    
            BatchUpdateReviewersView,
            BatchProgressSubmissionView,
    
    class SubmissionReviewerListView(AllActivityContextMixin, BaseReviewerSubmissionsTable):
        template_name = 'funds/submissions.html'
    
    
    class SubmissionListView(ViewDispatcher):
        admin_view = SubmissionAdminListView
        reviewer_view = SubmissionReviewerListView
    
    
    
    @method_decorator(staff_required, name='dispatch')
    
    class SubmissionsByRound(AllActivityContextMixin, BaseAdminSubmissionsTable, DelegateableListView):
    
        template_name = 'funds/submissions_by_round.html'
    
            BatchUpdateReviewersView,
            BatchProgressSubmissionView,
    
        excluded_fields = ('round', 'lead', 'fund')
    
        def get_form_kwargs(self):
            kwargs = super().get_form_kwargs()
            kwargs['round'] = self.obj
            return kwargs
    
    
        def get_queryset(self):
            # We want to only show lab or Rounds in this view, their base class is Page
    
            try:
                self.obj = Page.objects.get(pk=self.kwargs.get('pk')).specific
            except Page.DoesNotExist:
                raise Http404(_("No Round or Lab found matching the query"))
    
    
            if not isinstance(self.obj, (LabBase, RoundBase)):
                raise Http404(_("No Round or Lab found matching the query"))
            return super().get_queryset().filter(Q(round=self.obj) | Q(page=self.obj))
    
        def get_context_data(self, **kwargs):
            return super().get_context_data(object=self.obj, **kwargs)
    
    
    
    @method_decorator(staff_required, name='dispatch')
    
    class SubmissionsByStatus(BaseAdminSubmissionsTable, DelegateableListView):
    
        template_name = 'funds/submissions_by_status.html'
        status_mapping = PHASES_MAPPING
    
        form_views = [
            BatchUpdateReviewersView,
            BatchProgressSubmissionView,
        ]
    
        def dispatch(self, request, *args, **kwargs):
    
            self.status = kwargs.get('status')
    
            try:
                status_data = self.status_mapping[self.status]
            except KeyError:
                raise Http404(_("No statuses match the requested value"))
    
            self.status_name = status_data['name']
            self.statuses = status_data['statuses']
    
            return super().dispatch(request, *args, **kwargs)
    
        def get_filterset_kwargs(self, filterset_class, **kwargs):
            return super().get_filterset_kwargs(filterset_class, limit_statuses=self.statuses, **kwargs)
    
    
        def get_queryset(self):
    
            return super().get_queryset().filter(status__in=self.statuses)
    
    
        def get_context_data(self, **kwargs):
            return super().get_context_data(
    
                status=self.status_name,
                statuses=self.statuses,
    
    @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
    
            redirect = DeterminationCreateOrUpdateView.should_redirect(self.request, self.object, action)
            if redirect:
                return redirect
    
            self.object.perform_transition(action, self.request.user, request=self.request)
    
            return super().form_valid(form)
    
    @method_decorator(staff_required, name='dispatch')
    class ScreeningSubmissionView(DelegatedViewMixin, UpdateView):
        model = ApplicationSubmission
        form_class = ScreeningSubmissionForm
        context_name = 'screening_form'
    
        def form_valid(self, form):
    
    Erin Mullaney's avatar
    Erin Mullaney committed
            old = copy(self.get_object())
            response = super().form_valid(form)
    
            # Record activity
            messenger(
                MESSAGES.SCREENING,
                request=self.request,
                user=self.request.user,
                submission=self.object,
    
                related=str(old.screening_status),
    
    Erin Mullaney's avatar
    Erin Mullaney committed
            return response
    
    @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(
    
                for reviewer in form.instance.assigned.all()
    
            response = super().form_valid(form)
    
            new_reviewers = set(form.instance.assigned.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,
    
            if added:
                # Automatic workflow actions.
                action = None
                if self.object.status == INITIAL_STATE:
                    # Automatically transition the application to "Internal review".
                    action = self.object.workflow.stepped_phases[1][0].name
                elif self.object.status == 'proposal_discussion':
                    # Automatically transition the proposal to "Internal review".
                    action = 'proposal_internal_review'
    
                # If action is set run perform_transition().
                if action:
                    try:
                        self.object.perform_transition(
                            action,
                            self.request.user,
                            request=self.request,
                            notify=False,
                        )
                    except (PermissionDenied, KeyError):
                        pass
    
            return response
    
    
    
    @method_decorator(staff_required, name='dispatch')
    class UpdatePartnersView(DelegatedViewMixin, UpdateView):
        model = ApplicationSubmission
        form_class = UpdatePartnersForm
        context_name = 'partner_form'
    
        def form_valid(self, form):
            old_partners = set(self.get_object().partners.all())
            response = super().form_valid(form)
            new_partners = set(form.instance.partners.all())
    
            added = new_partners - old_partners
            removed = old_partners - new_partners
    
            messenger(
                MESSAGES.PARTNERS_UPDATED,
                request=self.request,
                user=self.request.user,
                submission=self.kwargs['submission'],
                added=added,
                removed=removed,
            )
    
    
            messenger(
                MESSAGES.PARTNERS_UPDATED_PARTNER,
                request=self.request,
                user=self.request.user,
                submission=self.kwargs['submission'],
                added=added,
                removed=removed,
            )
    
    
    class AdminSubmissionDetailView(ReviewContextMixin, ActivityContextMixin, DelegateableView, DetailView):
    
        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)
    
    
            public_page = self.object.get_from_parent('detail')()
    
            return super().get_context_data(
    
                other_submissions=other_submissions,
    
                **kwargs,
    
    class ReviewerSubmissionDetailView(ReviewContextMixin, ActivityContextMixin, DelegateableView, DetailView):
        template_name_suffix = '_reviewer_detail'
        model = ApplicationSubmission
        form_views = [CommentFormView]
    
    
        def dispatch(self, request, *args, **kwargs):
            submission = self.get_object()
            # If the requesting user submitted the application, return the Applicant view.
            # Reviewers and partners may somtimes be appliants as well.
            if submission.user == request.user:
                return ApplicantSubmissionDetailView.as_view()(request, *args, **kwargs)
            return super().dispatch(request, *args, **kwargs)
    
    
    class PartnerSubmissionDetailView(ReviewContextMixin, ActivityContextMixin, DelegateableView, DetailView):
    
        model = ApplicationSubmission
        form_views = [CommentFormView]
    
    
        def dispatch(self, request, *args, **kwargs):
            submission = self.get_object()
    
            # If the requesting user submitted the application, return the Applicant view.
            # Reviewers and partners may somtimes be appliants as well.
            if submission.user == request.user:
                return ApplicantSubmissionDetailView.as_view()(request, *args, **kwargs)
    
            # Only allow partners in the submission they are added as partners
    
            partner_has_access = submission.partners.filter(pk=request.user.pk).exists()
    
            return super().dispatch(request, *args, **kwargs)
    
    
    class CommunitySubmissionDetailView(ReviewContextMixin, ActivityContextMixin, DelegateableView, DetailView):
    
        template_name_suffix = '_community_detail'
    
        model = ApplicationSubmission
        form_views = [CommentFormView]
    
        def dispatch(self, request, *args, **kwargs):
            submission = self.get_object()
            # If the requesting user submitted the application, return the Applicant view.
            # Reviewers and partners may somtimes be appliants as well.
            if submission.user == request.user:
                return ApplicantSubmissionDetailView.as_view()(request, *args, **kwargs)
    
            # Only allow community reviewers in submission with a community review state.
    
            if not submission.community_review:
                raise PermissionDenied
            return super().dispatch(request, *args, **kwargs)
    
    
    class ApplicantSubmissionDetailView(ActivityContextMixin, DelegateableView, DetailView):
        model = ApplicationSubmission
        form_views = [CommentFormView]
    
        def get_object(self):
            return super().get_object().from_draft()
    
        def dispatch(self, request, *args, **kwargs):
            submission = self.get_object()
            # This view is only for applicants.
            if submission.user != request.user:
    
            return super().dispatch(request, *args, **kwargs)
    
    
    
    class SubmissionDetailView(ViewDispatcher):
        admin_view = AdminSubmissionDetailView
        reviewer_view = ReviewerSubmissionDetailView
        partner_view = PartnerSubmissionDetailView
        community_view = CommunitySubmissionDetailView
        applicant_view = ApplicantSubmissionDetailView
    
    
    
    @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 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):
    
            submission = self.get_object()
    
                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())
    
            try:
                transition = self.transitions[action.pop()]
            except KeyError:
                pass
            else:
                self.object.perform_transition(
                    transition.target,
                    self.request.user,
                    request=self.request,
                    notify=not (revision or submitting_proposal),  # Use the other notification
                )
    
            return HttpResponseRedirect(self.get_success_url())
    
    @method_decorator(login_required, name='dispatch')
    class PartnerSubmissionEditView(ApplicantSubmissionEditView):
        def dispatch(self, request, *args, **kwargs):
            submission = self.get_object()
    
            # If the requesting user submitted the application, return the Applicant view.
            # Partners may somtimes be appliants as well.
            if submission.user == request.user:
                return ApplicantSubmissionEditView.as_view()(request, *args, **kwargs)
    
            partner_has_access = submission.partners.filter(pk=request.user.pk).exists()
            if not partner_has_access:
                raise PermissionDenied
            return super(ApplicantSubmissionEditView, self).dispatch(request, *args, **kwargs)
    
    
    
    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_rendered_text_fields = self.object.render_text_blocks_answers()
    
            from_required = self.render_required()
    
    
            self.object.form_data = to_data.form_data
    
            to_rendered_text_fields = self.object.render_text_blocks_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.named_blocks, diffed_required):
    
                setattr(self.object, 'get_{}_display'.format(field), diff)
    
            diffed_text_fields_answers = [
    
                for fields in zip(from_rendered_text_fields, to_rendered_text_fields)
    
            self.object.output_answers = mark_safe(''.join(diffed_text_fields_answers))
    
        def render_required(self):
            return [
                getattr(self.object, 'get_{}_display'.format(field))()
    
                for field in self.object.named_blocks
    
        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)
    
    
    
    @method_decorator(staff_required, name='dispatch')
    
    class RoundListView(SingleTableMixin, FilterView):
    
        template_name = 'funds/rounds.html'
    
        table_class = RoundsTable
    
        filterset_class = RoundsFilter
    
    
        def get_queryset(self):
    
            return RoundsAndLabs.objects.with_progress()
    
    Parbhat Puri's avatar
    Parbhat Puri committed
    
    
    @method_decorator(permission_required('funds.delete_applicationsubmission', raise_exception=True), name='dispatch')
    class SubmissionDeleteView(DeleteView):
        model = ApplicationSubmission
        success_url = reverse_lazy('funds:submissions:list')
    
        def delete(self, request, *args, **kwargs):
            submission = self.get_object()
            messenger(
                MESSAGES.DELETE_SUBMISSION,
                user=request.user,
                request=request,
                submission=submission,
            )
            response = super().delete(request, *args, **kwargs)
            return response
    
    class SubmissionPrivateMediaView(UserPassesTestMixin, View):
    
        def get(self, *args, **kwargs):
    
            submission_id = kwargs['submission_id']
            field_id = kwargs['field_id']
            file_name = kwargs['file_name']
            file_name_with_path = f'submission/{submission_id}/{field_id}/{file_name}'
    
    
            submission_file = submission_storage.open(file_name_with_path)
            wrapper = FileWrapper(submission_file)
            encoding_map = {
                'bzip2': 'application/x-bzip',
                'gzip': 'application/gzip',
                'xz': 'application/x-xz',
            }
            content_type, encoding = mimetypes.guess_type(file_name)
            # Encoding isn't set to prevent browsers from automatically uncompressing files.
            content_type = encoding_map.get(encoding, content_type)
            content_type = content_type or 'application/octet-stream'
            # From Django 2.1, we can use FileResponse instead of StreamingHttpResponse
            response = StreamingHttpResponse(wrapper, content_type=content_type)
    
            response['Content-Disposition'] = f'filename={file_name}'
            response['Content-Length'] = submission_file.size
    
            return response
    
    
        def test_func(self):
            submission_id = self.kwargs['submission_id']
            submission = get_object_or_404(ApplicationSubmission, id=submission_id)
    
            return is_user_has_access_to_view_submission(self.request.user, submission)
    
        def handle_no_permission(self):
            # This method can be removed after upgrading Django to 2.1
            # https://github.com/django/django/commit/9b1125bfc7e2dc747128e6e7e8a2259ff1a7d39f
            # In older versions, authenticated users who lacked permissions were
            # redirected to the login page (which resulted in a loop) instead of
            # receiving an HTTP 403 Forbidden response.
            if self.raise_exception or self.request.user.is_authenticated:
                raise PermissionDenied(self.get_permission_denied_message())
            return redirect_to_login(self.request.get_full_path(), self.get_login_url(), self.get_redirect_field_name())