def review()

in src/olympia/reviewers/views.py [0:0]


def review(request, addon, channel=None):
    whiteboard_url = reverse(
        'reviewers.whiteboard',
        args=(channel or 'listed', addon.pk),
    )
    channel, content_review = determine_channel(channel)

    is_static_theme = addon.type == amo.ADDON_STATICTHEME
    promoted_groups = addon.promoted_groups(currently_approved=False)

    # Are we looking at an unlisted review page, or (weirdly) the listed
    # review page of an unlisted-only add-on?
    unlisted_only = channel == amo.CHANNEL_UNLISTED or not addon.has_listed_versions(
        include_deleted=True
    )
    if unlisted_only and not acl.is_unlisted_addons_viewer_or_reviewer(request.user):
        raise PermissionDenied

    # Are we looking at a listed review page while only having content review
    # permissions ? Redirect to content review page, it will be more useful.
    if (
        channel == amo.CHANNEL_LISTED
        and content_review is False
        and acl.action_allowed_for(request.user, amo.permissions.ADDONS_CONTENT_REVIEW)
        and not acl.is_reviewer(request.user, addon, allow_content_reviewers=False)
    ):
        return redirect('reviewers.review', 'content', addon.pk)

    # Other cases are handled in ReviewHelper by limiting what actions are
    # available depending on user permissions and add-on/version state.
    is_admin = acl.action_allowed_for(request.user, amo.permissions.REVIEWS_ADMIN)
    version = addon.find_latest_version(channel=channel, exclude=(), deleted=is_admin)
    latest_not_disabled_version = addon.find_latest_version(channel=channel)

    if not settings.ALLOW_SELF_REVIEWS and addon.has_author(request.user):
        amo.messages.warning(request, 'Self-reviews are not allowed.')
        return redirect(reverse('reviewers.dashboard'))

    needs_human_review_qs = NeedsHumanReview.objects.filter(
        is_active=True, version=OuterRef('pk')
    )
    # Queryset to be paginated for versions. We use the default ordering to get
    # most recently created first (Note that the template displays each page
    # in reverse order, older first).
    versions_qs = (
        # We want to load all Versions, even deleted ones, while using the
        # addon.versions related manager to get `addon` property pre-cached on
        # each version.
        addon.versions(manager='unfiltered_for_relations')
        .filter(channel=channel)
        .select_related('autoapprovalsummary')
        .select_related('reviewerflags')
        .select_related('file___webext_permissions')
        .select_related('blockversion')
        # Prefetch needshumanreview existence into a property that the
        # VersionsChoiceWidget will use.
        .annotate(needs_human_review=Exists(needs_human_review_qs))
        # Prefetch scanner results and related rules...
        .prefetch_related('scannerresults')
        .prefetch_related('scannerresults__matched_rules')
        # Add activity transformer to prefetch all related activity logs on
        # top of the regular transformers.
        .transform(Version.transformer_activity)
        # Add auto_approvable transformer to prefetch information about whether
        # each version is auto-approvable or not.
        .transform(Version.transformer_auto_approvable)
    )
    form_helper = ReviewHelper(
        addon=addon,
        version=version,
        user=request.user,
        content_review=content_review,
        human_review=True,
    )
    form = ReviewForm(
        request.POST if request.method == 'POST' else None,
        request.FILES if request.method == 'POST' else None,
        helper=form_helper,
    )

    reports = Paginator(AbuseReport.objects.for_addon(addon), 5).page(1)
    user_ratings = Paginator(
        (
            Rating.without_replies.filter(
                addon=addon, rating__lte=3, body__isnull=False
            ).order_by('-created')
        ),
        5,
    ).page(1)
    if channel == amo.CHANNEL_LISTED and is_static_theme:
        redirect_url = reverse('reviewers.queue_theme')
    else:
        channel_arg = (
            amo.CHANNEL_CHOICES_API.get(channel) if not content_review else 'content'
        )
        redirect_url = reverse('reviewers.review', args=[channel_arg, addon.pk])

    if request.method == 'POST' and form.is_valid():
        # Execute the action (is_valid() ensures the action is available to the
        # reviewer)
        form.helper.process()

        amo.messages.success(request, 'Review successfully processed.')
        clear_reviewing_cache(addon.id)
        return redirect(form.helper.redirect_url or redirect_url)

    # Kick off validation tasks for any files in this version which don't have
    # cached validation, since reviewers will almost certainly need to access
    # them. But only if we're not running in eager mode, since that could mean
    # blocking page load for several minutes.
    if version and not getattr(settings, 'CELERY_TASK_ALWAYS_EAGER', False):
        if not version.file.has_been_validated:
            devhub_tasks.validate(version.file)

    actions = form.helper.actions.items()

    # Find the previously approved version to compare to.
    base_version_pk = version and (
        addon.versions.exclude(id=version.id)
        .filter(
            # We're looking for a version that was either manually approved
            # (either it has no auto approval summary, or it has one but
            # with a negative verdict because it was locked by a reviewer
            # who then approved it themselves), or auto-approved but then
            # confirmed.
            Q(autoapprovalsummary__isnull=True)
            | Q(autoapprovalsummary__verdict=amo.NOT_AUTO_APPROVED)
            | Q(
                autoapprovalsummary__verdict=amo.AUTO_APPROVED,
                autoapprovalsummary__confirmed=True,
            )
        )
        .filter(
            channel=channel,
            file__isnull=False,
            created__lt=version.created,
            file__status=amo.STATUS_APPROVED,
        )
        .values_list('pk', flat=True)
        .order_by('-created')
        .first()
    )
    # The actions we shouldn't show a minimal form for.
    actions_full = []
    # The actions we should show the comments form for (contrary to minimal
    # form above, it defaults to True, because most actions do need to have
    # the comments form).
    actions_comments = []
    # The actions for which we should display the delayed rejection fields.
    actions_delayable = []
    # The actions for which we should display the reason select field.
    actions_reasons = []
    # The actions for which we should display the resolve abuse reports checkbox
    actions_resolves_cinder_jobs = []
    # The actions for which we should display the cinder policy select field.
    actions_policies = []
    # The actions for which to allow attachments.
    actions_attachments = []

    for key, action in actions:
        if not (is_static_theme or action.get('minimal')):
            actions_full.append(key)
        if action.get('comments', True):
            actions_comments.append(key)
        if action.get('delayable', False):
            actions_delayable.append(key)
        if action.get('allows_reasons', False):
            actions_reasons.append(key)
        if action.get('requires_policies', False):
            actions_policies.append(key)
        if action.get('resolves_cinder_jobs', False):
            actions_resolves_cinder_jobs.append(key)
        if action.get('can_attach', True):
            actions_attachments.append(key)

    addons_sharing_same_guid = (
        Addon.unfiltered.all()
        .only_translations()
        .filter(addonguid__guid=addon.addonguid_guid)
        .exclude(pk=addon.pk)
        .order_by('pk')
        if addon.addonguid_guid
        else []
    )
    approvals_info = None
    if (
        channel == amo.CHANNEL_LISTED
        and addon.current_version
        and addon.current_version.was_auto_approved
    ):
        try:
            approvals_info = addon.addonapprovalscounter
        except AddonApprovalsCounter.DoesNotExist:
            pass

    pager = paginate(request, versions_qs, VERSIONS_PER_REVIEW_PAGE)
    num_pages = pager.paginator.num_pages
    count = pager.paginator.count

    auto_approval_info = {}
    version_ids = []
    # Now that we've paginated the versions queryset, iterate on them to
    # generate auto approvals info. Note that the variable should not clash
    # the already existing 'version'.
    for a_version in pager.object_list:
        version_ids.append(a_version.pk)
        if not a_version.is_ready_for_auto_approval:
            continue
        try:
            summary = a_version.autoapprovalsummary
        except AutoApprovalSummary.DoesNotExist:
            auto_approval_info[a_version.pk] = None
            continue
        # Call calculate_verdict() again, it will use the data already stored.
        verdict_info = summary.calculate_verdict(pretty=True)
        auto_approval_info[a_version.pk] = verdict_info

    versions_pending_rejection_qs = versions_qs.filter(
        reviewerflags__pending_rejection__isnull=False
    )
    # We want to notify the reviewer if there are versions needing extra
    # attention that are not present in the versions history (which is
    # paginated).
    versions_with_a_due_date_other = (
        versions_qs.filter(due_date__isnull=False).exclude(pk__in=version_ids).count()
    )
    versions_flagged_by_mad_other = (
        versions_qs.filter(reviewerflags__needs_human_review_by_mad=True)
        .exclude(pk__in=version_ids)
        .count()
    )
    versions_pending_rejection_other = versions_pending_rejection_qs.exclude(
        pk__in=version_ids
    ).count()

    # if the add-on was force-disabled we want to inform the reviewer what versions will
    # be re-enabled automatically by a force enable action. (in all channels)
    versions_that_would_be_enabled = (
        ()
        if addon.status != amo.STATUS_DISABLED
        else File.objects.disabled_that_would_be_renabled_with_addon()
        .filter(version__addon=addon)
        .order_by('-created')
        .values_list(
            'version__version', 'original_status', 'version__channel', named=True
        )
    )

    flags = get_flags(addon, version) if version else []

    try:
        whiteboard = Whiteboard.objects.get(pk=addon.pk)
    except Whiteboard.DoesNotExist:
        whiteboard = Whiteboard(pk=addon.pk)

    wb_form_cls = PublicWhiteboardForm if is_static_theme else WhiteboardForm
    whiteboard_form = wb_form_cls(instance=whiteboard, prefix='whiteboard')

    # Actions that are not tied to a specific version that we want to highlight
    # in the "Add-on important changes history" section.
    addon_important_changes_actions = (
        # These are from developers themselves
        amo.LOG.ADD_USER_WITH_ROLE.id,
        amo.LOG.CHANGE_USER_WITH_ROLE.id,
        amo.LOG.REMOVE_USER_WITH_ROLE.id,
        # These are from admins or reviewers
        amo.LOG.FORCE_DISABLE.id,
        amo.LOG.HELD_ACTION_FORCE_DISABLE.id,
        amo.LOG.FORCE_ENABLE.id,
        amo.LOG.REQUEST_ADMIN_REVIEW_THEME.id,
        amo.LOG.CLEAR_ADMIN_REVIEW_THEME.id,
        amo.LOG.REQUEST_LEGAL.id,
    )
    important_changes_log = ActivityLog.objects.filter(
        action__in=addon_important_changes_actions,
        addonlog__addon=addon,
    ).order_by('id')

    name_translations = (
        addon.name.__class__.objects.filter(
            id=addon.name.id, localized_string__isnull=False
        ).exclude(localized_string='')
        if addon.name
        else []
    )

    Addon._attach_authors([addon], listed=None, to_attr='current_authors')

    ctx = context(
        # Used for reviewer subscription check, don't use global `is_reviewer`
        # since that actually is `is_user_any_kind_of_reviewer`.
        acl_is_reviewer=acl.is_reviewer(request.user, addon),
        acl_is_unlisted_addons_viewer_or_reviewer=(
            acl.is_unlisted_addons_viewer_or_reviewer(request.user)
        ),
        acl_is_review_moderator=(
            acl.action_allowed_for(request.user, amo.permissions.RATINGS_MODERATE)
            and request.user.is_staff
        ),
        actions=actions,
        actions_attachments=actions_attachments,
        actions_comments=actions_comments,
        actions_delayable=actions_delayable,
        actions_full=actions_full,
        actions_policies=actions_policies,
        actions_reasons=actions_reasons,
        actions_resolves_cinder_jobs=actions_resolves_cinder_jobs,
        addon=addon,
        addons_sharing_same_guid=addons_sharing_same_guid,
        approvals_info=approvals_info,
        auto_approval_info=auto_approval_info,
        base_version_pk=base_version_pk,
        channel=channel,
        content_review=content_review,
        count=count,
        flags=flags,
        form=form,
        format_matched_rules=formatted_matched_rules_with_files_and_data,
        has_versions_with_due_date_in_other_channel=addon.versions(
            manager='unfiltered_for_relations'
        )
        .exclude(channel=channel)
        .filter(due_date__isnull=False)
        .exists(),
        important_changes_log=important_changes_log,
        is_admin=is_admin,
        language_dict=dict(settings.LANGUAGES),
        latest_not_disabled_version=latest_not_disabled_version,
        latest_version_is_unreviewed_and_not_pending_rejection=(
            version
            and version.channel == amo.CHANNEL_LISTED
            and version.is_unreviewed
            and not version.pending_rejection
        ),
        promoted_groups=promoted_groups,
        name_translations=name_translations,
        now=datetime.now(),
        num_pages=num_pages,
        pager=pager,
        reports=reports,
        session_id=request.session.session_key,
        subscribed_listed=ReviewerSubscription.objects.filter(
            user=request.user, addon=addon, channel=amo.CHANNEL_LISTED
        ).exists(),
        subscribed_unlisted=ReviewerSubscription.objects.filter(
            user=request.user, addon=addon, channel=amo.CHANNEL_UNLISTED
        ).exists(),
        unlisted=(channel == amo.CHANNEL_UNLISTED),
        user_ratings=user_ratings,
        version=version,
        VERSION_ADU_LIMIT=VERSION_ADU_LIMIT,
        versions_that_would_be_enabled=versions_that_would_be_enabled,
        MAX_VERSIONS_SHOWN_INLINE=MAX_VERSIONS_SHOWN_INLINE,
        versions_with_a_due_date_other=versions_with_a_due_date_other,
        versions_flagged_by_mad_other=versions_flagged_by_mad_other,
        versions_pending_rejection_other=versions_pending_rejection_other,
        whiteboard_form=whiteboard_form,
        whiteboard_url=whiteboard_url,
    )
    return TemplateResponse(request, 'reviewers/review.html', context=ctx)