Esempio n. 1
0
    def process(self, version):
        """Process a single version, figuring out if it should be auto-approved
        and calling the approval code if necessary."""
        already_locked = AutoApprovalSummary.check_is_locked(version)
        if not already_locked:
            # Lock the addon for ourselves if possible. Even though
            # AutoApprovalSummary.create_summary_for_version() will do
            # call check_is_locked() again later when calculating the verdict,
            # we have to do it now to prevent overwriting an existing lock with
            # our own.
            set_reviewing_cache(version.addon.pk, settings.TASK_USER_ID)
        try:
            log.info('Processing %s version %s...',
                     unicode(version.addon.name), unicode(version.version))
            summary, info = AutoApprovalSummary.create_summary_for_version(
                version, dry_run=self.dry_run)
            log.info('Auto Approval for %s version %s: %s',
                     unicode(version.addon.name), unicode(version.version),
                     summary.get_verdict_display())
            self.stats.update({k: int(v) for k, v in info.items()})
            if summary.verdict == self.successful_verdict:
                self.stats['auto_approved'] += 1
                if summary.verdict == amo.AUTO_APPROVED:
                    self.approve(version)

        except (AutoApprovalNotEnoughFilesError,
                AutoApprovalNoValidationResultError):
            log.info(
                'Version %s was skipped either because it had no '
                'file or because it had no validation attached.', version)
            self.stats['error'] += 1
        finally:
            # Always clear our own lock no matter what happens (but only ours).
            if not already_locked:
                clear_reviewing_cache(version.addon.pk)
Esempio n. 2
0
 def process_addon(self, *, addon, now):
     latest_version = addon.find_latest_version(
         channel=amo.RELEASE_CHANNEL_LISTED)
     if (latest_version and latest_version.is_unreviewed
             and not latest_version.pending_rejection):
         # If latest version is unreviewed and not pending
         # rejection, we want to put the delayed rejection of all
         # versions of this addon on hold until a decision has been
         # made by reviewers on the latest one.
         log.info(
             'Skipping rejections for add-on %s until version %s '
             'has been reviewed', addon.pk, latest_version.pk)
         return
     versions = self.fetch_version_candidates_for_addon(addon=addon,
                                                        now=now)
     if not versions.exists():
         log.info('Somehow no versions to auto-reject for add-on %s',
                  addon.pk)
         return
     locked_by = get_reviewing_cache(addon.pk)
     if locked_by:
         # Don't auto-reject something that has been locked, even by the
         # task user - wait until it's free to avoid any conflicts.
         log.info(
             'Skipping rejections for add-on %s until lock from user %s '
             'has expired', addon.pk, locked_by)
         return
         set_reviewing_cache(addon.pk, settings.TASK_USER_ID)
     try:
         self.reject_versions(addon=addon,
                              versions=versions,
                              latest_version=latest_version)
     finally:
         # Always clear our lock no matter what happens.
         clear_reviewing_cache(addon.pk)
Esempio n. 3
0
    def process(self, version):
        """Process a single version, figuring out if it should be auto-approved
        and calling the approval code if necessary."""
        already_locked = AutoApprovalSummary.check_is_locked(version)
        if not already_locked:
            # Lock the addon for ourselves if possible. Even though
            # AutoApprovalSummary.create_summary_for_version() will do
            # call check_is_locked() again later when calculating the verdict,
            # we have to do it now to prevent overwriting an existing lock with
            # our own.
            set_reviewing_cache(version.addon.pk, settings.TASK_USER_ID)
        try:
            with transaction.atomic():
                log.info('Processing %s version %s...',
                         six.text_type(version.addon.name),
                         six.text_type(version.version))
                summary, info = AutoApprovalSummary.create_summary_for_version(
                    version, dry_run=self.dry_run)
                self.stats.update({k: int(v) for k, v in info.items()})
                if summary.verdict == self.successful_verdict:
                    if summary.verdict == amo.AUTO_APPROVED:
                        self.approve(version)
                    self.stats['auto_approved'] += 1
                    verdict_string = summary.get_verdict_display()
                else:
                    verdict_string = '%s (%s)' % (
                        summary.get_verdict_display(), ', '.join(
                            summary.verdict_info_prettifier(info)))
                log.info('Auto Approval for %s version %s: %s',
                         six.text_type(version.addon.name),
                         six.text_type(version.version), verdict_string)

        # At this point, any exception should have rolled back the transaction,
        # so even if we did create/update an AutoApprovalSummary instance that
        # should have been rolled back. This ensures that, for instance, a
        # signing error doesn't leave the version and its autoapprovalsummary
        # in conflicting states.
        except (AutoApprovalNotEnoughFilesError,
                AutoApprovalNoValidationResultError):
            log.info(
                'Version %s was skipped either because it had no '
                'files or because it had no validation attached.', version)
            self.stats['error'] += 1
        except SigningError:
            log.info('Version %s was skipped because of a signing error',
                     version)
            self.stats['error'] += 1
        finally:
            # Always clear our own lock no matter what happens (but only ours).
            if not already_locked:
                clear_reviewing_cache(version.addon.pk)
Esempio n. 4
0
    def process(self, version):
        """Process a single version, figuring out if it should be auto-approved
        and calling the approval code if necessary."""
        already_locked = AutoApprovalSummary.check_is_locked(version)
        if not already_locked:
            # Lock the addon for ourselves if possible. Even though
            # AutoApprovalSummary.create_summary_for_version() will do
            # call check_is_locked() again later when calculating the verdict,
            # we have to do it now to prevent overwriting an existing lock with
            # our own.
            set_reviewing_cache(version.addon.pk, settings.TASK_USER_ID)
        try:
            with transaction.atomic():
                log.info('Processing %s version %s...',
                         six.text_type(version.addon.name),
                         six.text_type(version.version))
                summary, info = AutoApprovalSummary.create_summary_for_version(
                    version, dry_run=self.dry_run)
                log.info('Auto Approval for %s version %s: %s',
                         six.text_type(version.addon.name),
                         six.text_type(version.version),
                         summary.get_verdict_display())
                self.stats.update({k: int(v) for k, v in info.items()})
                if summary.verdict == self.successful_verdict:
                    if summary.verdict == amo.AUTO_APPROVED:
                        self.approve(version)
                    self.stats['auto_approved'] += 1

        # At this point, any exception should have rolled back the transaction,
        # so even if we did create/update an AutoApprovalSummary instance that
        # should have been rolled back. This ensures that, for instance, a
        # signing error doesn't leave the version and its autoapprovalsummary
        # in conflicting states.
        except (AutoApprovalNotEnoughFilesError,
                AutoApprovalNoValidationResultError):
            log.info(
                'Version %s was skipped either because it had no '
                'files or because it had no validation attached.', version)
            self.stats['error'] += 1
        except SigningError:
            log.info(
                'Version %s was skipped because of a signing error', version)
            self.stats['error'] += 1
        finally:
            # Always clear our own lock no matter what happens (but only ours).
            if not already_locked:
                clear_reviewing_cache(version.addon.pk)
    def process(self, version):
        """Process a single version, figuring out if it should be auto-approved
        and calling the approval code if necessary."""
        already_locked = AutoApprovalSummary.check_is_locked(version)
        if not already_locked:
            # Lock the addon for ourselves if possible. Even though
            # AutoApprovalSummary.create_summary_for_version() will do
            # call check_is_locked() again later when calculating the verdict,
            # we have to do it now to prevent overwriting an existing lock with
            # our own.
            set_reviewing_cache(version.addon.pk, settings.TASK_USER_ID)
        try:
            log.info('Processing %s version %s...',
                     unicode(version.addon.name), unicode(version.version))
            summary, info = AutoApprovalSummary.create_summary_for_version(
                version, dry_run=self.dry_run)
            log.info('Auto Approval for %s version %s: %s',
                     unicode(version.addon.name),
                     unicode(version.version),
                     summary.get_verdict_display())
            self.stats.update({k: int(v) for k, v in info.items()})
            if summary.verdict == self.successful_verdict:
                self.stats['auto_approved'] += 1
                if summary.verdict == amo.AUTO_APPROVED:
                    self.approve(version)

        except (AutoApprovalNotEnoughFilesError,
                AutoApprovalNoValidationResultError):
            log.info(
                'Version %s was skipped either because it had no '
                'file or because it had no validation attached.', version)
            self.stats['error'] += 1
        finally:
            # Always clear our own lock no matter what happens (but only ours).
            if not already_locked:
                clear_reviewing_cache(version.addon.pk)
Esempio n. 6
0
def review(request, addon, channel=None):
    whiteboard_url = reverse('reviewers.whiteboard',
                             args=(channel or 'listed',
                                   addon.slug if addon.slug else addon.pk))
    channel, content_review_only = determine_channel(channel)

    was_auto_approved = (channel == amo.RELEASE_CHANNEL_LISTED
                         and addon.current_version
                         and addon.current_version.was_auto_approved)
    is_static_theme = addon.type == amo.ADDON_STATICTHEME

    # If we're just looking (GET) we can bypass the specific permissions checks
    # if we have ReviewerTools:View.
    bypass_more_specific_permissions_because_read_only = (
        request.method == 'GET'
        and acl.action_allowed(request, amo.permissions.REVIEWER_TOOLS_VIEW))

    if not bypass_more_specific_permissions_because_read_only:
        perform_review_permission_checks(
            request, addon, channel, content_review_only=content_review_only)

    version = addon.find_latest_version(channel=channel,
                                        exclude=(amo.STATUS_BETA, ))

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

    # Get the current info request state to set as the default.
    form_initial = {'info_request': addon.pending_info_request}

    form_helper = ReviewHelper(request=request,
                               addon=addon,
                               version=version,
                               content_review_only=content_review_only)
    form = ReviewForm(request.POST if request.method == 'POST' else None,
                      helper=form_helper,
                      initial=form_initial)
    is_admin = acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN)

    approvals_info = None
    reports = None
    user_ratings = None
    if channel == amo.RELEASE_CHANNEL_LISTED:
        if was_auto_approved:
            try:
                approvals_info = addon.addonapprovalscounter
            except AddonApprovalsCounter.DoesNotExist:
                pass

        developers = addon.listed_authors
        reports = Paginator((AbuseReport.objects.filter(
            Q(addon=addon) | Q(user__in=developers)).order_by('-created')),
                            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 content_review_only:
            queue_type = 'content_review'
        elif was_auto_approved:
            queue_type = 'auto_approved'
        else:
            queue_type = form.helper.handler.review_type
        redirect_url = reverse('reviewers.queue_%s' % queue_type)
    else:
        redirect_url = reverse('reviewers.unlisted_queue_all')

    if request.method == 'POST' and form.is_valid():
        form.helper.process()
        amo.messages.success(request,
                             ugettext('Review successfully processed.'))
        clear_reviewing_cache(addon.id)
        return redirect(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_ALWAYS_EAGER', False):
        for file_ in version.all_files:
            if not file_.has_been_validated:
                devhub_tasks.validate(file_)

    actions = form.helper.actions.items()

    try:
        # Find the previously approved version to compare to.
        show_diff = 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,
                    files__isnull=False,
                    created__lt=version.created,
                    files__status=amo.STATUS_PUBLIC).latest())
    except Version.DoesNotExist:
        show_diff = None

    # The actions we shouldn't show a minimal form for.
    actions_full = [
        k for (k, a) in actions if not (is_static_theme or a.get('minimal'))
    ]

    # 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 = [k for (k, a) in actions if a.get('comments', True)]

    versions = (Version.unfiltered.filter(
        addon=addon,
        channel=channel).select_related('autoapprovalsummary').exclude(
            files__status=amo.STATUS_BETA).order_by('-created').transform(
                Version.transformer_activity).transform(Version.transformer))

    # We assume comments on old deleted versions are for listed versions.
    # See _get_comments_for_hard_deleted_versions above for more detail.
    all_versions = (_get_comments_for_hard_deleted_versions(addon)
                    if channel == amo.RELEASE_CHANNEL_LISTED else [])
    all_versions.extend(versions)
    all_versions.sort(key=lambda v: v.created, reverse=True)

    pager = amo.utils.paginate(request, all_versions, 10)
    num_pages = pager.paginator.num_pages
    count = pager.paginator.count

    auto_approval_info = {}
    # 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:
        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

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

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

        whiteboard_form = WhiteboardForm(instance=whiteboard,
                                         prefix='whiteboard')
    else:
        whiteboard_form = None

    backgrounds = version.get_background_image_urls() if version else []

    user_changes_actions = [
        amo.LOG.ADD_USER_WITH_ROLE.id, amo.LOG.CHANGE_USER_WITH_ROLE.id,
        amo.LOG.REMOVE_USER_WITH_ROLE.id
    ]
    user_changes_log = AddonLog.objects.filter(
        activity_log__action__in=user_changes_actions,
        addon=addon).order_by('id')
    ctx = context(request,
                  actions=actions,
                  actions_comments=actions_comments,
                  actions_full=actions_full,
                  addon=addon,
                  api_token=request.COOKIES.get(API_TOKEN_COOKIE, None),
                  approvals_info=approvals_info,
                  auto_approval_info=auto_approval_info,
                  backgrounds=backgrounds,
                  content_review_only=content_review_only,
                  count=count,
                  flags=flags,
                  form=form,
                  is_admin=is_admin,
                  num_pages=num_pages,
                  pager=pager,
                  reports=reports,
                  show_diff=show_diff,
                  subscribed=ReviewerSubscription.objects.filter(
                      user=request.user, addon=addon).exists(),
                  unlisted=(channel == amo.RELEASE_CHANNEL_UNLISTED),
                  user_changes=user_changes_log,
                  user_ratings=user_ratings,
                  version=version,
                  was_auto_approved=was_auto_approved,
                  whiteboard_form=whiteboard_form,
                  whiteboard_url=whiteboard_url)
    return render(request, 'reviewers/review.html', ctx)
Esempio n. 7
0
def review(request, addon, channel=None):
    if channel == 'content':
        # 'content' is not a real channel, just a different review mode for
        # listed add-ons.
        content_review_only = True
        channel = 'listed'
    else:
        content_review_only = False
    # channel is passed in as text, but we want the constant.
    channel = amo.CHANNEL_CHOICES_LOOKUP.get(channel,
                                             amo.RELEASE_CHANNEL_LISTED)

    if content_review_only and not acl.action_allowed(
            request, amo.permissions.ADDONS_CONTENT_REVIEW):
        raise PermissionDenied

    unlisted_only = (channel == amo.RELEASE_CHANNEL_UNLISTED
                     or not addon.has_listed_versions())
    if unlisted_only and not acl.check_unlisted_addons_reviewer(request):
        raise PermissionDenied

    version = addon.find_latest_version(channel=channel,
                                        exclude=(amo.STATUS_BETA, ))

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

    # Get the current info request state to set as the default.
    form_initial = {'info_request': version and version.has_info_request}

    form_helper = ReviewHelper(request=request,
                               addon=addon,
                               version=version,
                               content_review_only=content_review_only)
    form = forms.ReviewForm(request.POST if request.method == 'POST' else None,
                            helper=form_helper,
                            initial=form_initial)
    is_admin = acl.action_allowed(request, amo.permissions.ADDONS_EDIT)
    is_post_reviewer = acl.action_allowed(request,
                                          amo.permissions.ADDONS_POST_REVIEW)

    approvals_info = None
    reports = None
    user_reviews = None
    was_auto_approved = False
    if channel == amo.RELEASE_CHANNEL_LISTED:
        if addon.current_version:
            was_auto_approved = addon.current_version.was_auto_approved
        if is_post_reviewer and version and version.is_webextension:
            try:
                approvals_info = addon.addonapprovalscounter
            except AddonApprovalsCounter.DoesNotExist:
                pass

        developers = addon.listed_authors
        reports = Paginator((AbuseReport.objects.filter(
            Q(addon=addon) | Q(user__in=developers)).order_by('-created')),
                            5).page(1)
        user_reviews = Paginator((Review.without_replies.filter(
            addon=addon, rating__lte=3,
            body__isnull=False).order_by('-created')), 5).page(1)

        if content_review_only:
            queue_type = 'content_review'
        elif was_auto_approved and is_post_reviewer:
            queue_type = 'auto_approved'
        else:
            queue_type = form.helper.handler.review_type
        redirect_url = reverse('reviewers.queue_%s' % queue_type)
    else:
        redirect_url = reverse('reviewers.unlisted_queue_all')

    if request.method == 'POST' and form.is_valid():
        form.helper.process()
        if form.cleaned_data.get('notify'):
            ReviewerSubscription.objects.get_or_create(user=request.user,
                                                       addon=addon)
        if form.cleaned_data.get('adminflag') and is_admin:
            addon.update(admin_review=False)
        amo.messages.success(request,
                             ugettext('Review successfully processed.'))
        clear_reviewing_cache(addon.id)
        return redirect(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_ALWAYS_EAGER', False):
        for file_ in version.all_files:
            if not file_.has_been_validated:
                devhub_tasks.validate(file_)

    canned = AddonCannedResponse.objects.all()
    actions = form.helper.actions.items()

    try:
        # Find the previously approved version to compare to.
        show_diff = version and (addon.versions.exclude(id=version.id).filter(
            # We're looking for a version that was either manually approved
            # or auto-approved but then confirmed.
            Q(autoapprovalsummary__isnull=True)
            | Q(autoapprovalsummary__verdict=amo.AUTO_APPROVED,
                autoapprovalsummary__confirmed=True)).filter(
                    channel=channel,
                    files__isnull=False,
                    created__lt=version.created,
                    files__status=amo.STATUS_PUBLIC).latest())
    except Version.DoesNotExist:
        show_diff = None

    # The actions we should show a minimal form for.
    actions_minimal = [k for (k, a) in actions if not a.get('minimal')]

    # 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 = [k for (k, a) in actions if a.get('comments', True)]

    # The actions we should show the 'info request' checkbox for.
    actions_info_request = [
        k for (k, a) in actions if a.get('info_request', False)
    ]

    versions = (Version.unfiltered.filter(
        addon=addon,
        channel=channel).select_related('autoapprovalsummary').exclude(
            files__status=amo.STATUS_BETA).order_by('-created').transform(
                Version.transformer_activity).transform(Version.transformer))

    # We assume comments on old deleted versions are for listed versions.
    # See _get_comments_for_hard_deleted_versions above for more detail.
    all_versions = (_get_comments_for_hard_deleted_versions(addon)
                    if channel == amo.RELEASE_CHANNEL_LISTED else [])
    all_versions.extend(versions)
    all_versions.sort(key=lambda v: v.created, reverse=True)

    pager = amo.utils.paginate(request, all_versions, 10)
    num_pages = pager.paginator.num_pages
    count = pager.paginator.count

    auto_approval_info = {}
    # 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:
        if not is_post_reviewer or 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

    if version:
        flags = get_flags(version)
    else:
        flags = []

    user_changes_actions = [
        amo.LOG.ADD_USER_WITH_ROLE.id, amo.LOG.CHANGE_USER_WITH_ROLE.id,
        amo.LOG.REMOVE_USER_WITH_ROLE.id
    ]
    user_changes_log = AddonLog.objects.filter(
        activity_log__action__in=user_changes_actions,
        addon=addon).order_by('id')
    ctx = context(request,
                  version=version,
                  addon=addon,
                  pager=pager,
                  num_pages=num_pages,
                  count=count,
                  flags=flags,
                  form=form,
                  canned=canned,
                  is_admin=is_admin,
                  show_diff=show_diff,
                  actions=actions,
                  actions_minimal=actions_minimal,
                  actions_comments=actions_comments,
                  actions_info_request=actions_info_request,
                  whiteboard_form=forms.WhiteboardForm(instance=addon),
                  user_changes=user_changes_log,
                  unlisted=(channel == amo.RELEASE_CHANNEL_UNLISTED),
                  approvals_info=approvals_info,
                  is_post_reviewer=is_post_reviewer,
                  auto_approval_info=auto_approval_info,
                  reports=reports,
                  user_reviews=user_reviews,
                  was_auto_approved=was_auto_approved,
                  content_review_only=content_review_only)

    return render(request, 'reviewers/review.html', ctx)
Esempio n. 8
0
    def process(self, version):
        """Process a single version, figuring out if it should be auto-approved
        and calling the approval code if necessary."""
        already_locked = AutoApprovalSummary.check_is_locked(version)
        if not already_locked:
            # Lock the addon for ourselves if possible. Even though
            # AutoApprovalSummary.create_summary_for_version() will do
            # call check_is_locked() again later when calculating the verdict,
            # we have to do it now to prevent overwriting an existing lock with
            # our own.
            set_reviewing_cache(version.addon.pk, settings.TASK_USER_ID)

        # Discard any existing celery tasks that may have been queued before:
        # If there are any left at this point, it means the transaction from
        # the previous loop iteration was not committed and we shouldn't
        # trigger the corresponding tasks.
        _discard_tasks()
        # Queue celery tasks for this version, avoiding triggering them too
        # soon...
        _start_queuing_tasks()

        try:
            with transaction.atomic():
                # ...and release the queued tasks to celery once transaction
                # is committed.
                transaction.on_commit(_send_tasks_and_stop_queuing)

                log.info(
                    'Processing %s version %s...',
                    str(version.addon.name),
                    str(version.version),
                )

                if waffle.switch_is_active('run-action-in-auto-approve'):
                    # We want to execute `run_action()` only once.
                    summary_exists = AutoApprovalSummary.objects.filter(
                        version=version).exists()
                    if summary_exists:
                        log.info('Not running run_action() because it has '
                                 'already been executed')
                    else:
                        ScannerResult.run_action(version)

                summary, info = AutoApprovalSummary.create_summary_for_version(
                    version, dry_run=self.dry_run)
                self.stats.update({k: int(v) for k, v in info.items()})
                if summary.verdict == self.successful_verdict:
                    if summary.verdict == amo.AUTO_APPROVED:
                        self.approve(version)
                    self.stats['auto_approved'] += 1
                    verdict_string = summary.get_verdict_display()
                else:
                    verdict_string = '%s (%s)' % (
                        summary.get_verdict_display(),
                        ', '.join(summary.verdict_info_prettifier(info)),
                    )
                log.info(
                    'Auto Approval for %s version %s: %s',
                    str(version.addon.name),
                    str(version.version),
                    verdict_string,
                )

        # At this point, any exception should have rolled back the transaction,
        # so even if we did create/update an AutoApprovalSummary instance that
        # should have been rolled back. This ensures that, for instance, a
        # signing error doesn't leave the version and its autoapprovalsummary
        # in conflicting states.
        except (AutoApprovalNotEnoughFilesError,
                AutoApprovalNoValidationResultError):
            log.info(
                'Version %s was skipped either because it had no '
                'files or because it had no validation attached.',
                version,
            )
            self.stats['error'] += 1
        except SigningError:
            statsd.incr('reviewers.auto_approve.approve.failure')
            log.info('Version %s was skipped because of a signing error',
                     version)
            self.stats['error'] += 1
        finally:
            # Always clear our own lock no matter what happens (but only ours).
            if not already_locked:
                clear_reviewing_cache(version.addon.pk)
            # Stop post request task queue before moving on (useful in tests to
            # leave a fresh state for the next test. Note that we don't want to
            # send or clear queued tasks (they may belong to a transaction that
            # has been rolled back, or they may not have been processed by the
            # on commit handler yet).
            _stop_queuing_tasks()
Esempio n. 9
0
def review(request, addon, channel=None):
    whiteboard_url = reverse(
        'reviewers.whiteboard',
        args=(channel or 'listed', addon.slug if addon.slug else addon.pk))
    channel, content_review_only = determine_channel(channel)

    was_auto_approved = (
        channel == amo.RELEASE_CHANNEL_LISTED and
        addon.current_version and addon.current_version.was_auto_approved)
    is_static_theme = addon.type == amo.ADDON_STATICTHEME

    # If we're just looking (GET) we can bypass the specific permissions checks
    # if we have ReviewerTools:View.
    bypass_more_specific_permissions_because_read_only = (
        request.method == 'GET' and acl.action_allowed(
            request, amo.permissions.REVIEWER_TOOLS_VIEW))

    if not bypass_more_specific_permissions_because_read_only:
        perform_review_permission_checks(
            request, addon, channel, content_review_only=content_review_only)

    version = addon.find_latest_version(channel=channel, exclude=())

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

    # Get the current info request state to set as the default.
    form_initial = {'info_request': addon.pending_info_request}

    form_helper = ReviewHelper(
        request=request, addon=addon, version=version,
        content_review_only=content_review_only)
    form = ReviewForm(request.POST if request.method == 'POST' else None,
                      helper=form_helper, initial=form_initial)
    is_admin = acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN)

    approvals_info = None
    reports = None
    user_ratings = None
    if channel == amo.RELEASE_CHANNEL_LISTED:
        if was_auto_approved:
            try:
                approvals_info = addon.addonapprovalscounter
            except AddonApprovalsCounter.DoesNotExist:
                pass

        developers = addon.listed_authors
        reports = Paginator(
            (AbuseReport.objects
                        .filter(Q(addon=addon) | Q(user__in=developers))
                        .order_by('-created')), 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 content_review_only:
            queue_type = 'content_review'
        elif was_auto_approved:
            queue_type = 'auto_approved'
        else:
            queue_type = form.helper.handler.review_type
        redirect_url = reverse('reviewers.queue_%s' % queue_type)
    else:
        redirect_url = reverse('reviewers.unlisted_queue_all')

    if request.method == 'POST' and form.is_valid():
        form.helper.process()
        amo.messages.success(
            request, ugettext('Review successfully processed.'))
        clear_reviewing_cache(addon.id)
        return redirect(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_ALWAYS_EAGER', False):
        for file_ in version.all_files:
            if not file_.has_been_validated:
                devhub_tasks.validate(file_)

    actions = form.helper.actions.items()

    try:
        # Find the previously approved version to compare to.
        show_diff = 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,
                files__isnull=False,
                created__lt=version.created,
                files__status=amo.STATUS_PUBLIC).latest())
    except Version.DoesNotExist:
        show_diff = None

    # The actions we shouldn't show a minimal form for.
    actions_full = [
        k for (k, a) in actions if not (is_static_theme or a.get('minimal'))]

    # 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 = [k for (k, a) in actions if a.get('comments', True)]

    versions = (Version.unfiltered.filter(addon=addon, channel=channel)
                                  .select_related('autoapprovalsummary')
                                  .order_by('-created')
                                  .transform(Version.transformer_activity)
                                  .transform(Version.transformer))

    # We assume comments on old deleted versions are for listed versions.
    # See _get_comments_for_hard_deleted_versions above for more detail.
    all_versions = (_get_comments_for_hard_deleted_versions(addon)
                    if channel == amo.RELEASE_CHANNEL_LISTED else [])
    all_versions.extend(versions)
    all_versions.sort(key=lambda v: v.created,
                      reverse=True)

    pager = amo.utils.paginate(request, all_versions, 10)
    num_pages = pager.paginator.num_pages
    count = pager.paginator.count

    auto_approval_info = {}
    # 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:
        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

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

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

        whiteboard_form = WhiteboardForm(
            instance=whiteboard, prefix='whiteboard')
    else:
        whiteboard_form = None

    backgrounds = version.get_background_image_urls() if version else []

    user_changes_actions = [
        amo.LOG.ADD_USER_WITH_ROLE.id,
        amo.LOG.CHANGE_USER_WITH_ROLE.id,
        amo.LOG.REMOVE_USER_WITH_ROLE.id]
    user_changes_log = AddonLog.objects.filter(
        activity_log__action__in=user_changes_actions,
        addon=addon).order_by('id')
    ctx = context(
        request, actions=actions, actions_comments=actions_comments,
        actions_full=actions_full, addon=addon,
        api_token=request.COOKIES.get(API_TOKEN_COOKIE, None),
        approvals_info=approvals_info, auto_approval_info=auto_approval_info,
        backgrounds=backgrounds, content_review_only=content_review_only,
        count=count, flags=flags, form=form, is_admin=is_admin,
        num_pages=num_pages, pager=pager, reports=reports, show_diff=show_diff,
        subscribed=ReviewerSubscription.objects.filter(
            user=request.user, addon=addon).exists(),
        unlisted=(channel == amo.RELEASE_CHANNEL_UNLISTED),
        user_changes=user_changes_log, user_ratings=user_ratings,
        version=version, was_auto_approved=was_auto_approved,
        whiteboard_form=whiteboard_form,
        whiteboard_url=whiteboard_url)
    return render(request, 'reviewers/review.html', ctx)