Example #1
0
    def has_read_developer_agreement(self):
        from olympia.zadmin.models import get_config

        # Fallback date in case the config date value is invalid or set to the
        # future. The current fallback date is when we enabled post-review.
        dev_agreement_change_fallback = datetime(2017, 9, 22, 17, 36)

        if self.read_dev_agreement is None:
            return False
        try:
            last_agreement_change_config = get_config(
                'last_dev_agreement_change_date')
            change_config_date = datetime.strptime(
                last_agreement_change_config, '%Y-%m-%d %H:%M')

            # If the config date is in the future, instead check against the
            # fallback date
            if change_config_date > datetime.now():
                return self.read_dev_agreement > dev_agreement_change_fallback

            return self.read_dev_agreement > change_config_date
        except (ValueError, TypeError):
            log.exception('last_developer_agreement_change misconfigured, '
                          '"%s" is not a '
                          'datetime' % last_agreement_change_config)
            return self.read_dev_agreement > dev_agreement_change_fallback
Example #2
0
def motd(request):
    form = None
    if acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit'):
        form = forms.MOTDForm(
            initial={'motd': get_config('editors_review_motd')})
    motd_editable = acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit')
    data = context(request, form=form, motd_editable=motd_editable)
    return render(request, 'editors/motd.html', data)
Example #3
0
def motd(request):
    form = None
    motd_editable = acl.action_allowed(
        request, amo.permissions.ADDON_REVIEWER_MOTD_EDIT)
    if motd_editable:
        form = forms.MOTDForm(
            initial={'motd': get_config('editors_review_motd')})
    data = context(request, form=form, motd_editable=motd_editable)
    return render(request, 'editors/motd.html', data)
Example #4
0
    def handle(self, *args, **options):
        """Command entry point."""
        self.post_review = waffle.switch_is_active('post-review')
        self.dry_run = options.get('dry_run', False)
        self.max_average_daily_users = int(
            get_config('AUTO_APPROVAL_MAX_AVERAGE_DAILY_USERS') or 0)
        self.min_approved_updates = int(
            get_config('AUTO_APPROVAL_MIN_APPROVED_UPDATES') or 0)

        if self.min_approved_updates <= 0 or self.max_average_daily_users <= 0:
            # Auto approval are shut down if one of those values is not present
            # or <= 0.
            url = '%s%s' % (
                settings.SITE_URL,
                reverse('admin:zadmin_config_changelist'))
            raise CommandError(
                'Auto-approvals are deactivated because either '
                'AUTO_APPROVAL_MAX_AVERAGE_DAILY_USERS or '
                'AUTO_APPROVAL_MIN_APPROVED_UPDATES have not been '
                'set or were set to 0. Use the admin tools Config model to '
                'set them by going to %s.' % url)

        self.successful_verdict = (
            amo.WOULD_HAVE_BEEN_AUTO_APPROVED if self.dry_run
            else amo.AUTO_APPROVED)

        self.stats = Counter()

        # Get a lock before doing anything, we don't want to have multiple
        # instances of the command running in parallel.
        lock = atomic_lock(settings.TMP_PATH, LOCK_NAME, lifetime=15 * 60)
        with lock as lock_attained:
            if lock_attained:
                qs = self.fetch_candidates()
                self.stats['total'] = len(qs)

                for version in qs:
                    self.process(version)

                self.log_final_summary(self.stats)
            else:
                # We didn't get the lock...
                log.error('auto-approve lock present, aborting.')
Example #5
0
def base_context(**kw):
    ctx = {'motd': get_config('editors_review_motd')}
    ctx.update(kw)
    return ctx
Example #6
0
def motd(request):
    form = None
    form = MOTDForm(initial={'motd': get_config('reviewers_review_motd')})
    data = context(request, form=form)
    return render(request, 'reviewers/motd.html', data)
def global_settings(request):
    """
    Storing standard AMO-wide information used in global headers, such as
    account links and settings.
    """
    account_links = []
    tools_links = []
    context = {}

    tools_title = ugettext('Tools')
    is_reviewer = False

    if request.user.is_authenticated():
        is_reviewer = acl.is_user_any_kind_of_reviewer(request.user)

        account_links.append({'text': ugettext('My Profile'),
                              'href': request.user.get_url_path()})
        if request.user.is_artist:
            account_links.append({'text': ugettext('My Themes'),
                                  'href': request.user.get_themes_url_path()})

        account_links.append({'text': ugettext('Account Settings'),
                              'href': reverse('users.edit')})
        account_links.append({
            'text': ugettext('My Collections'),
            'href': reverse('collections.user', args=[request.user.username])})

        if request.user.favorite_addons:
            account_links.append(
                {'text': ugettext('My Favorites'),
                 'href': reverse('collections.detail',
                                 args=[request.user.username, 'favorites'])})

        account_links.append({
            'text': ugettext('Log out'),
            'href': reverse('users.logout') + '?to=' + urlquote(request.path),
        })

        if request.user.is_developer:
            tools_links.append({'text': ugettext('Manage My Submissions'),
                                'href': reverse('devhub.addons')})
        tools_links.append(
            {'text': ugettext('Submit a New Add-on'),
             'href': reverse('devhub.submit.agreement')})
        no_more_lwt = waffle.switch_is_active('disable-lwt-uploads')
        tools_links.append(
            {'text': ugettext('Submit a New Theme'),
             'href': reverse('devhub.submit.agreement' if no_more_lwt
                             else 'devhub.themes.submit')})
        tools_links.append(
            {'text': ugettext('Developer Hub'),
             'href': reverse('devhub.index')})
        tools_links.append(
            {'text': ugettext('Manage API Keys'),
             'href': reverse('devhub.api_key')}
        )

        if is_reviewer:
            tools_links.append({'text': ugettext('Reviewer Tools'),
                                'href': reverse('reviewers.dashboard')})
        if acl.action_allowed(request, amo.permissions.ANY_ADMIN):
            tools_links.append({'text': ugettext('Admin Tools'),
                                'href': reverse('zadmin.index')})

        context['user'] = request.user
    else:
        context['user'] = AnonymousUser()

    context.update({'account_links': account_links,
                    'settings': settings,
                    'amo': amo,
                    'tools_links': tools_links,
                    'tools_title': tools_title,
                    'ADMIN_MESSAGE': get_config('site_notice'),
                    'is_reviewer': is_reviewer})
    return context
Example #8
0
def base_context(**kw):
    ctx = {'motd': get_config('editors_review_motd')}
    ctx.update(kw)
    return ctx
Example #9
0
    def handle(self, *args, **options):
        dry_run = options.get('dry_run', False)
        max_average_daily_users = int(
            get_config('AUTO_APPROVAL_MAX_AVERAGE_DAILY_USERS') or 0)
        min_approved_updates = int(
            get_config('AUTO_APPROVAL_MIN_APPROVED_UPDATES') or 0)

        if min_approved_updates <= 0 or max_average_daily_users <= 0:
            # Auto approval are shut down if one of those values is not present
            # or <= 0.
            url = '%s%s' % (settings.SITE_URL,
                            reverse('admin:zadmin_config_changelist'))
            raise CommandError(
                'Auto-approvals are deactivated because either '
                'AUTO_APPROVAL_MAX_AVERAGE_DAILY_USERS or '
                'AUTO_APPROVAL_MIN_APPROVED_UPDATES have not been '
                'set or were set to 0. Use the admin tools Config model to '
                'set them by going to %s.' % url)

        stats = Counter()
        qs = self.fetch_candidates()
        stats['total'] = len(qs)

        successful_verdict = (amo.WOULD_HAVE_BEEN_AUTO_APPROVED
                              if dry_run else amo.AUTO_APPROVED)

        for version in qs:
            # Is the addon already locked by a reviewer ?
            if get_reviewing_cache(version.addon.pk):
                stats['locked'] += 1
                continue

            # If admin review or more information was requested, skip this
            # version, let a human handle it.
            if version.addon.admin_review or version.has_info_request:
                stats['flagged'] += 1
                continue

            # Lock the addon for ourselves, no reviewer should touch it.
            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,
                    max_average_daily_users=max_average_daily_users,
                    min_approved_updates=min_approved_updates,
                    dry_run=dry_run)
                log.info('Auto Approval for %s version %s: %s',
                         unicode(version.addon.name), unicode(version.version),
                         summary.get_verdict_display())
                stats.update({k: int(v) for k, v in info.items()})
                if summary.verdict == successful_verdict:
                    stats['auto_approved'] += 1
                # FIXME: implement auto-approve if verdict is amo.AUTO_APPROVED

            except (AutoApprovalNotEnoughFilesError,
                    AutoApprovalNoValidationResultError):
                log.info(
                    'Version %s was skipped either because it had no '
                    'file or because it had no validation attached.', version)
                stats['error'] += 1
            finally:
                clear_reviewing_cache(version.addon.pk)

        self.log_final_summary(stats, dry_run=dry_run)
Example #10
0
def _upload_mlbf_to_remote_settings(*, force_base=False):
    last_generation_time = get_config(MLBF_TIME_CONFIG_KEY, 0, json_value=True)

    log.info('Starting Upload MLBF to remote settings cron job.')

    # This timestamp represents the point in time when all previous addon
    # guid + versions and blocks were used to generate the bloomfilter.
    # An add-on version/file from before this time will definitely be accounted
    # for in the bloomfilter so we can reliably assert if it's blocked or not.
    # An add-on version/file from after this time can't be reliably asserted -
    # there may be false positives or false negatives.
    # https://github.com/mozilla/addons-server/issues/13695
    generation_time = int(time.time() * 1000)
    mlbf = MLBF(generation_time)
    previous_filter = MLBF(last_generation_time)

    changes_count = mlbf.blocks_changed_since_previous(previous_filter)
    statsd.incr(
        'blocklist.cron.upload_mlbf_to_remote_settings.blocked_changed',
        changes_count)
    need_update = (
        force_base or
        last_generation_time < get_blocklist_last_modified_time() or
        changes_count)
    if not need_update:
        log.info(
            'No new/modified/deleted Blocks in database; '
            'skipping MLBF generation')
        return

    mlbf.generate_and_write_mlbf()
    statsd.incr(
        'blocklist.cron.upload_mlbf_to_remote_settings.blocked_count',
        len(mlbf.fetch_blocked_json()))
    statsd.incr(
        'blocklist.cron.upload_mlbf_to_remote_settings.not_blocked_count',
        len(mlbf.fetch_not_blocked_json()))

    base_filter_id = get_config(MLBF_BASE_ID_CONFIG_KEY, 0, json_value=True)
    # optimize for when the base_filter was the previous generation so
    # we don't have to load the blocked JSON file twice.
    base_filter = (
        MLBF(base_filter_id)
        if last_generation_time != base_filter_id else
        previous_filter)

    make_base_filter = (
        force_base or
        not base_filter or
        mlbf.should_reset_base_filter(base_filter))

    if last_generation_time and not make_base_filter:
        try:
            mlbf.write_stash(previous_filter)
        except FileNotFoundError:
            log.info('No previous blocked.json so we can\'t create a stash.')
            # fallback to creating a new base if stash fails
            make_base_filter = True

    upload_filter.delay(
        generation_time,
        is_base=make_base_filter,
        upload_stash=not make_base_filter)
Example #11
0
def motd(request):
    form = None
    form = MOTDForm(initial={'motd': get_config('reviewers_review_motd')})
    data = context(request, form=form)
    return render(request, 'reviewers/motd.html', data)
Example #12
0
    def test_waffle_off_disables_publishing(self):
        upload_mlbf_to_kinto()

        self.publish_attachment_mock.assert_not_called()
        self.publish_record_mock.assert_not_called()
        assert not get_config(MLBF_TIME_CONFIG_KEY)
Example #13
0
def global_settings(request):
    """
    Storing standard AMO-wide information used in global headers, such as
    account links and settings.
    """
    account_links = []
    tools_links = []
    context = {}

    tools_title = _('Tools')
    is_reviewer = False

    if request.user.is_authenticated():
        user = request.user

        profile = request.user
        is_reviewer = (acl.check_addons_reviewer(request)
                       or acl.check_personas_reviewer(request))

        account_links.append({
            'text': _('My Profile'),
            'href': profile.get_url_path()
        })
        if user.is_artist:
            account_links.append({
                'text': _('My Themes'),
                'href': profile.get_user_url('themes')
            })

        account_links.append({
            'text': _('Account Settings'),
            'href': reverse('users.edit')
        })
        account_links.append({
            'text':
            _('My Collections'),
            'href':
            reverse('collections.user', args=[user.username])
        })

        if user.favorite_addons:
            account_links.append({
                'text':
                _('My Favorites'),
                'href':
                reverse('collections.detail',
                        args=[user.username, 'favorites'])
            })

        account_links.append({
            'text':
            _('Log out'),
            'href':
            reverse('users.logout') + '?to=' + urlquote(request.path),
        })

        if request.user.is_developer:
            tools_links.append({
                'text': _('Manage My Submissions'),
                'href': reverse('devhub.addons')
            })
        links = [
            {
                'text': _('Submit a New Add-on'),
                'href': reverse('devhub.submit.1')
            },
            {
                'text': _('Submit a New Theme'),
                'href': reverse('devhub.themes.submit')
            },
            {
                'text': _('Developer Hub'),
                'href': reverse('devhub.index')
            },
        ]
        links.append({
            'text': _('Manage API Keys'),
            'href': reverse('devhub.api_key')
        })

        tools_links += links
        if is_reviewer:
            tools_links.append({
                'text': _('Editor Tools'),
                'href': reverse('editors.home')
            })
        if (acl.action_allowed(request, 'Admin', '%')
                or acl.action_allowed(request, 'AdminTools', 'View')):
            tools_links.append({
                'text': _('Admin Tools'),
                'href': reverse('zadmin.home')
            })

        context['user'] = request.user
    else:
        context['user'] = AnonymousUser()

    context.update({
        'account_links': account_links,
        'settings': settings,
        'amo': amo,
        'tools_links': tools_links,
        'tools_title': tools_title,
        'ADMIN_MESSAGE': get_config('site_notice'),
        'is_reviewer': is_reviewer
    })
    return context
Example #14
0
def review(request, addon, channel=None):
    # channel is passed in as text, but we want the constant.
    channel = amo.CHANNEL_CHOICES_LOOKUP.get(
        channel, amo.RELEASE_CHANNEL_LISTED)
    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('editors.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)
    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 was_auto_approved and is_post_reviewer:
            queue_type = 'auto_approved'
        else:
            queue_type = form.helper.handler.review_type
        redirect_url = reverse('editors.queue_%s' % queue_type)
    else:
        redirect_url = reverse('editors.unlisted_queue_all')

    if request.method == 'POST' and form.is_valid():
        form.helper.process()
        if form.cleaned_data.get('notify'):
            EditorSubscription.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 editors 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

    max_average_daily_users = int(
        get_config('AUTO_APPROVAL_MAX_AVERAGE_DAILY_USERS') or 0)
    min_approved_updates = int(
        get_config('AUTO_APPROVAL_MIN_APPROVED_UPDATES') or 0)
    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.
        # Need to pass max_average_daily_users and min_approved_updates current
        # values.
        verdict_info = summary.calculate_verdict(
            max_average_daily_users=max_average_daily_users,
            min_approved_updates=min_approved_updates,
            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)

    return render(request, 'editors/review.html', ctx)
Example #15
0
 def to_representation(self, obj):
     return {
         'read_only': settings.READ_ONLY,
         'notice': get_config('site_notice'),
     }
Example #16
0
def review(request, addon, channel=None):
    # channel is passed in as text, but we want the constant.
    channel = amo.CHANNEL_CHOICES_LOOKUP.get(channel,
                                             amo.RELEASE_CHANNEL_LISTED)
    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('editors.queue'))

    form_helper = ReviewHelper(request=request, addon=addon, version=version)
    form = forms.ReviewForm(request.POST if request.method == 'POST' else None,
                            helper=form_helper)
    if channel == amo.RELEASE_CHANNEL_LISTED:
        queue_type = form.helper.handler.review_type
        redirect_url = reverse('editors.queue_%s' % queue_type)
    else:
        redirect_url = reverse('editors.unlisted_queue_all')

    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 request.method == 'POST' and form.is_valid():
        form.helper.process()
        if form.cleaned_data.get('notify'):
            EditorSubscription.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 editors 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:
        show_diff = version and (addon.versions.exclude(id=version.id).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)]

    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

    max_average_daily_users = int(
        get_config('AUTO_APPROVAL_MAX_AVERAGE_DAILY_USERS') or 0),
    min_approved_updates = int(
        get_config('AUTO_APPROVAL_MIN_APPROVED_UPDATES') or 0)
    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.
        # Need to pass max_average_daily_users and min_approved_updates current
        # values.
        verdict_info = summary.calculate_verdict(
            max_average_daily_users=max_average_daily_users,
            min_approved_updates=min_approved_updates,
            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,
                  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)

    return render(request, 'editors/review.html', ctx)
def global_settings(request):
    """
    Storing standard AMO-wide information used in global headers, such as
    account links and settings.
    """
    account_links = []
    tools_links = []
    context = {}

    tools_title = _('Tools')
    is_reviewer = False

    if request.user.is_authenticated():
        user = request.user
        profile = request.user
        is_reviewer = (acl.check_addons_reviewer(request) or
                       acl.check_personas_reviewer(request))

        account_links.append({'text': _('My Profile'),
                              'href': profile.get_url_path()})
        if user.is_artist:
            account_links.append({'text': _('My Themes'),
                                  'href': profile.get_user_url('themes')})

        account_links.append({'text': _('Account Settings'),
                              'href': reverse('users.edit')})
        account_links.append({
            'text': _('My Collections'),
            'href': reverse('collections.user', args=[user.username])})

        if user.favorite_addons:
            account_links.append(
                {'text': _('My Favorites'),
                 'href': reverse('collections.detail',
                                 args=[user.username, 'favorites'])})

        account_links.append({
            'text': _('Log out'),
            'href': reverse('users.logout') + '?to=' + urlquote(request.path),
        })

        if request.user.is_developer:
            tools_links.append({'text': _('Manage My Submissions'),
                                'href': reverse('devhub.addons')})
        links = [
            {'text': _('Submit a New Add-on'),
             'href': reverse('devhub.submit.1')},
            {'text': _('Submit a New Theme'),
             'href': reverse('devhub.themes.submit')},
            {'text': _('Developer Hub'),
             'href': reverse('devhub.index')},
        ]
        if waffle.switch_is_active('signing-api'):
            links.append({'text': _('Manage API Keys'),
                          'href': reverse('devhub.api_key')})

        tools_links += links
        if is_reviewer:
            tools_links.append({'text': _('Editor Tools'),
                                'href': reverse('editors.home')})
        if (acl.action_allowed(request, 'Admin', '%') or
                acl.action_allowed(request, 'AdminTools', 'View')):
            tools_links.append({'text': _('Admin Tools'),
                                'href': reverse('zadmin.home')})

        context['user'] = request.user
    else:
        context['user'] = AnonymousUser()

    context.update({'account_links': account_links,
                    'settings': settings, 'amo': amo,
                    'tools_links': tools_links,
                    'tools_title': tools_title,
                    'ADMIN_MESSAGE': get_config('site_notice'),
                    'is_reviewer': is_reviewer})
    return context
def global_settings(request):
    """
    Storing standard AMO-wide information used in global headers, such as
    account links and settings.
    """
    account_links = []
    tools_links = []
    context = {}

    tools_title = ugettext('Tools')
    is_reviewer = False

    if request.user.is_authenticated():
        is_reviewer = acl.is_user_any_kind_of_reviewer(request.user)

        account_links.append({'text': ugettext('My Profile'),
                              'href': request.user.get_url_path()})
        if request.user.is_artist:
            account_links.append({'text': ugettext('My Themes'),
                                  'href': request.user.get_themes_url_path()})

        account_links.append({'text': ugettext('Account Settings'),
                              'href': reverse('users.edit')})
        account_links.append({
            'text': ugettext('My Collections'),
            'href': reverse('collections.user', args=[request.user.username])})

        if request.user.favorite_addons:
            account_links.append(
                {'text': ugettext('My Favorites'),
                 'href': reverse('collections.detail',
                                 args=[request.user.username, 'favorites'])})

        account_links.append({
            'text': ugettext('Log out'),
            'href': reverse('users.logout') + '?to=' + urlquote(request.path),
        })

        if request.user.is_developer:
            tools_links.append({'text': ugettext('Manage My Submissions'),
                                'href': reverse('devhub.addons')})
        links = [
            {'text': ugettext('Submit a New Add-on'),
             'href': reverse('devhub.submit.agreement')},
            {'text': ugettext('Submit a New Theme'),
             'href': reverse('devhub.themes.submit')},
            {'text': ugettext('Developer Hub'),
             'href': reverse('devhub.index')},
        ]
        links.append({'text': ugettext('Manage API Keys'),
                      'href': reverse('devhub.api_key')})

        tools_links += links
        if is_reviewer:
            tools_links.append({'text': ugettext('Reviewer Tools'),
                                'href': reverse('reviewers.dashboard')})
        if (acl.action_allowed(request, amo.permissions.ADMIN) or
                acl.action_allowed(request, amo.permissions.ADMIN_TOOLS_VIEW)):
            tools_links.append({'text': ugettext('Admin Tools'),
                                'href': reverse('zadmin.index')})

        context['user'] = request.user
    else:
        context['user'] = AnonymousUser()

    context.update({'account_links': account_links,
                    'settings': settings,
                    'amo': amo,
                    'tools_links': tools_links,
                    'tools_title': tools_title,
                    'ADMIN_MESSAGE': get_config('site_notice'),
                    'is_reviewer': is_reviewer})
    return context