Exemplo n.º 1
0
def check_stats_permission(request, addon, for_contributions=False):
    """
    Check if user is allowed to view stats for ``addon``.

    Raises PermissionDenied if user is not allowed.
    """
    # If public, non-contributions: everybody can view.
    if addon.public_stats and not for_contributions:
        return

    # Everything else requires an authenticated user.
    if not request.user.is_authenticated():
        raise PermissionDenied

    if not for_contributions:
        # Only authors and Stats Viewers allowed.
        if (addon.has_author(request.user) or
                acl.action_allowed(request, 'Stats', 'View')):
            return

    else:  # For contribution stats.
        # Only authors and Contribution Stats Viewers.
        if (addon.has_author(request.user) or
                acl.action_allowed(request, 'RevenueStats', 'View')):
            return

    raise PermissionDenied
Exemplo n.º 2
0
def _queue(request, TableObj, tab, qs=None, unlisted=False,
           SearchForm=QueueSearchForm):
    if qs is None:
        qs = TableObj.Meta.model.objects.all()

    if SearchForm:
        if request.GET:
            search_form = SearchForm(request.GET)
            if search_form.is_valid():
                qs = search_form.filter_qs(qs)
        else:
            search_form = SearchForm()
        is_searching = search_form.data.get('searching')
    else:
        search_form = None
        is_searching = False
    admin_reviewer = is_admin_reviewer(request)

    # Those restrictions will only work with our RawSQLModel, so we need to
    # make sure we're not dealing with a regular Django ORM queryset first.
    if hasattr(qs, 'sql_model'):
        if not is_searching and not admin_reviewer:
            qs = filter_admin_review_for_legacy_queue(qs)
        if not unlisted:
            if is_limited_reviewer(request):
                qs = qs.having(
                    'waiting_time_hours >=', amo.REVIEW_LIMITED_DELAY_HOURS)

            qs = filter_static_themes(
                qs, acl.action_allowed(request, amo.permissions.ADDONS_REVIEW),
                acl.action_allowed(
                    request, amo.permissions.STATIC_THEMES_REVIEW))
            # Most WebExtensions are picked up by auto_approve cronjob, they
            # don't need to appear in the queues, unless auto approvals have
            # been disabled for them.  Webextension static themes aren't auto
            # approved.
            qs = qs.filter(
                Q(addon_type_id=amo.ADDON_STATICTHEME) |
                Q(**{'files.is_webextension': False}) |
                Q(**{'addons_addonreviewerflags.auto_approval_disabled': True})
            )

    order_by = request.GET.get('sort', TableObj.default_order_by())
    if hasattr(TableObj, 'translate_sort_cols'):
        order_by = TableObj.translate_sort_cols(order_by)
    table = TableObj(data=qs, order_by=order_by)
    per_page = request.GET.get('per_page', REVIEWS_PER_PAGE)
    try:
        per_page = int(per_page)
    except ValueError:
        per_page = REVIEWS_PER_PAGE
    if per_page <= 0 or per_page > REVIEWS_PER_PAGE_MAX:
        per_page = REVIEWS_PER_PAGE
    page = paginate(request, table.rows, per_page=per_page)
    table.set_page(page)
    return render(request, 'reviewers/queue.html',
                  context(request, table=table, page=page, tab=tab,
                          search_form=search_form,
                          point_types=amo.REVIEWED_AMO,
                          unlisted=unlisted))
Exemplo n.º 3
0
    def test_tools_developer_and_admin(self):
        # Make them a developer.
        user = self.login('admin', get=True)
        AddonUser.objects.create(user=user, addon=Addon.objects.all()[0])

        response = self.client.get(self.url, follow=True)
        assert response.status_code == 200
        request = response.context['request']
        assert request.user.is_developer
        assert acl.action_allowed(request, amo.permissions.ADDONS_REVIEW)
        assert acl.action_allowed(request, amo.permissions.LOCALIZER)
        assert acl.action_allowed(request, amo.permissions.ANY_ADMIN)

        expected = [
            ('Tools', '#'),
            ('Manage My Submissions', reverse('devhub.addons')),
            ('Submit a New Add-on', reverse('devhub.submit.agreement')),
            ('Submit a New Theme', reverse('devhub.themes.submit')),
            ('Developer Hub', reverse('devhub.index')),
            ('Manage API Keys', reverse('devhub.api_key')),
            ('Reviewer Tools', reverse('reviewers.dashboard')),
            ('Admin Tools', reverse('zadmin.index')),
        ]
        check_links(
            expected, pq(response.content)('#aux-nav .tools a'), verify=False)
Exemplo n.º 4
0
def user_can_delete_review(request, review):
    """Return whether or not the request.user can delete reviews.

    People who can delete reviews:
      * The original review author.
      * Reviewers with Ratings:Moderate, if the review has been flagged and
        they are not an author of this add-on.
      * Users in a group with "Users:Edit" or "Addons:Edit" privileges and
        they are not an author of this add-on.
    """
    is_rating_author = review.user_id == request.user.id
    is_addon_author = review.addon.has_author(request.user)
    is_moderator = (
        acl.action_allowed(request, amo.permissions.RATINGS_MODERATE) and
        review.editorreview
    )
    can_edit_users_or_addons = (
        acl.action_allowed(request, amo.permissions.USERS_EDIT) or
        acl.action_allowed(request, amo.permissions.ADDONS_EDIT)
    )

    return (
        is_rating_author or
        (not is_addon_author and (is_moderator or can_edit_users_or_addons))
    )
Exemplo n.º 5
0
 def wrapper(request, *args, **kw):
     from olympia.access import acl
     if (acl.action_allowed(request, '*', '*') or
             not acl.action_allowed(request, 'Restricted', 'UGC')):
         return f(request, *args, **kw)
     else:
         raise PermissionDenied
Exemplo n.º 6
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)
Exemplo n.º 7
0
def home(request):
    if (not acl.action_allowed(request, amo.permissions.ADDONS_REVIEW) and
            acl.action_allowed(request, amo.permissions.THEMES_REVIEW)):
        return http.HttpResponseRedirect(reverse('editors.themes.home'))

    motd_editable = acl.action_allowed(
        request, amo.permissions.ADDON_REVIEWER_MOTD_EDIT)
    durations = (('new', ugettext('New Add-ons (Under 5 days)')),
                 ('med', ugettext('Passable (5 to 10 days)')),
                 ('old', ugettext('Overdue (Over 10 days)')))

    limited_reviewer = is_limited_reviewer(request)
    progress, percentage = _editor_progress(limited_reviewer=limited_reviewer)
    reviews_max_display = getattr(settings, 'EDITOR_REVIEWS_MAX_DISPLAY', 5)
    reviews_total = ActivityLog.objects.total_reviews()[:reviews_max_display]
    reviews_monthly = (
        ActivityLog.objects.monthly_reviews()[:reviews_max_display])
    reviews_total_count = ActivityLog.objects.user_approve_reviews(
        request.user).count()
    reviews_monthly_count = (
        ActivityLog.objects.current_month_user_approve_reviews(
            request.user).count())

    # Try to read user position from retrieved reviews.
    # If not available, query for it.
    reviews_total_position = (
        ActivityLog.objects.user_position(reviews_total, request.user) or
        ActivityLog.objects.total_reviews_user_position(request.user))

    reviews_monthly_position = (
        ActivityLog.objects.user_position(reviews_monthly, request.user) or
        ActivityLog.objects.monthly_reviews_user_position(request.user))

    limited_reviewer = is_limited_reviewer(request)
    data = context(
        request,
        reviews_total=reviews_total,
        reviews_monthly=reviews_monthly,
        reviews_total_count=reviews_total_count,
        reviews_monthly_count=reviews_monthly_count,
        reviews_total_position=reviews_total_position,
        reviews_monthly_position=reviews_monthly_position,
        new_editors=EventLog.new_editors(),
        eventlog=ActivityLog.objects.editor_events()[:6],
        progress=progress,
        percentage=percentage,
        durations=durations,
        reviews_max_display=reviews_max_display,
        motd_editable=motd_editable,
        queue_counts_total=queue_counts(admin_reviewer=True,
                                        limited_reviewer=limited_reviewer),
    )

    return render(request, 'editors/home.html', data)
Exemplo n.º 8
0
def context(request, **kw):
    admin_reviewer = is_admin_reviewer(request)
    extension_reviews = acl.action_allowed(
        request, amo.permissions.ADDONS_REVIEW)
    theme_reviews = acl.action_allowed(
        request, amo.permissions.STATIC_THEMES_REVIEW)
    ctx = {
        'queue_counts': queue_counts(admin_reviewer=admin_reviewer,
                                     extension_reviews=extension_reviews,
                                     theme_reviews=theme_reviews),
    }
    ctx.update(base_context(**kw))
    return ctx
Exemplo n.º 9
0
def queue_tabnav_themes_interactive(context):
    """Tabnav for the interactive shiny theme queues."""
    tabs = []
    if acl.action_allowed(context['request'], 'Personas', 'Review'):
        tabs.append((
            'editors.themes.queue_themes', 'pending', _('Pending'),
        ))
    if acl.action_allowed(context['request'], 'SeniorPersonasTools', 'View'):
        tabs.append((
            'editors.themes.queue_flagged', 'flagged', _('Flagged'),
        ))
        tabs.append((
            'editors.themes.queue_rereview', 'rereview', _('Updates'),
        ))
    return tabs
Exemplo n.º 10
0
def queue_tabnav(context):
    """Returns tuple of tab navigation for the queue pages.

    Each tuple contains three elements: (tab_code, page_url, tab_text)
    """
    counts = context['queue_counts']
    request = context['request']
    listed = not context.get('unlisted')

    if listed:
        tabnav = [('nominated', 'queue_nominated',
                   (ungettext('New Add-on ({0})',
                              'New Add-ons ({0})',
                              counts['nominated'])
                    .format(counts['nominated']))),
                  ('pending', 'queue_pending',
                   (ungettext('Update ({0})',
                              'Updates ({0})',
                              counts['pending'])
                    .format(counts['pending']))),
                  ('moderated', 'queue_moderated',
                   (ungettext('Moderated Review ({0})',
                              'Moderated Reviews ({0})',
                              counts['moderated'])
                    .format(counts['moderated'])))]

        if acl.action_allowed(request, amo.permissions.ADDONS_POST_REVIEW):
            tabnav.append(
                ('auto_approved', 'queue_auto_approved',
                 (ungettext('Auto Approved Add-on ({0})',
                            'Auto Approved Add-ons ({0})',
                            counts['auto_approved'])
                  .format(counts['auto_approved']))),
            )
        if acl.action_allowed(request, amo.permissions.ADDONS_CONTENT_REVIEW):
            tabnav.append(
                ('content_review', 'queue_content_review',
                 (ungettext('Content Review ({0})',
                            'Content Review ({0})',
                            counts['content_review'])
                  .format(counts['content_review']))),
            )
    else:
        tabnav = [
            ('all', 'unlisted_queue_all', ugettext('All Unlisted Add-ons'))
        ]

    return tabnav
Exemplo n.º 11
0
def eventlog_detail(request, id):
    log = get_object_or_404(ActivityLog.objects.editor_events(), pk=id)

    review = None
    # I really cannot express the depth of the insanity incarnate in
    # our logging code...
    if len(log.arguments) > 1 and isinstance(log.arguments[1], Review):
        review = log.arguments[1]

    is_admin = acl.action_allowed(request, 'ReviewerAdminTools', 'View')

    can_undelete = review and review.deleted and (
        is_admin or request.user.pk == log.user.pk)

    if request.method == 'POST':
        # A Form seems overkill for this.
        if request.POST['action'] == 'undelete':
            if not can_undelete:
                raise PermissionDenied

            ReviewerScore.award_moderation_points(
                log.user, review.addon, review.id, undo=True)
            review.undelete()
        return redirect('editors.eventlog.detail', id)

    data = context(request, log=log, can_undelete=can_undelete)
    return render(request, 'editors/eventlog_detail.html', data)
Exemplo n.º 12
0
def reply(request, addon, review_id):
    is_admin = acl.action_allowed(request, 'Addons', 'Edit')
    is_author = acl.check_addon_ownership(request, addon, dev=True)
    if not (is_admin or is_author):
        raise PermissionDenied

    review = get_object_or_404(Review.objects, pk=review_id, addon=addon)
    form = forms.ReviewReplyForm(request.POST or None)
    if request.method == 'POST' and form.is_valid():
        d = dict(reply_to=review, addon=addon,
                 defaults=dict(user=request.user))
        reply, new = Review.objects.get_or_create(**d)
        for key, val in _review_details(request, addon, form).items():
            setattr(reply, key, val)
        reply.save()
        action = 'New' if new else 'Edited'
        log.debug('%s reply to %s: %s' % (action, review_id, reply.id))

        if new:
            reply_url = helpers.url('addons.reviews.detail', addon.slug,
                                    review.id, add_prefix=False)
            data = {'name': addon.name,
                    'reply_title': reply.title,
                    'reply': reply.body,
                    'reply_url': helpers.absolutify(reply_url)}
            emails = [review.user.email]
            sub = u'Mozilla Add-on Developer Reply: %s' % addon.name
            send_mail('reviews/emails/reply_review.ltxt',
                      sub, emails, Context(data), 'reply')

        return redirect(helpers.url('addons.reviews.detail', addon.slug,
                                    review_id))
    ctx = dict(review=review, form=form, addon=addon)
    return render(request, 'reviews/reply.html', ctx)
Exemplo n.º 13
0
 def wrapper(request, *args, **kw):
     allow_access = (
         acl.is_user_any_kind_of_reviewer(request.user) or
         acl.action_allowed(request, permissions.RATINGS_MODERATE))
     if allow_access:
         return f(request, *args, **kw)
     raise PermissionDenied
Exemplo n.º 14
0
def queue_moderated(request):
    qs = Review.objects.all().to_moderate().order_by('reviewflag__created')
    page = paginate(request, qs, per_page=20)
    motd_editable = acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit')

    flags = dict(ReviewFlag.FLAGS)

    reviews_formset = ReviewFlagFormSet(request.POST or None,
                                        queryset=page.object_list,
                                        request=request)

    if request.method == 'POST':
        if reviews_formset.is_valid():
            reviews_formset.save()
        else:
            amo.messages.error(
                request, ' '.join(e.as_text() or _('An unknown error occurred')
                                  for e in reviews_formset.errors))
        return redirect(reverse('editors.queue_moderated'))

    return render(request, 'editors/queue.html',
                  context(request, reviews_formset=reviews_formset,
                          tab='moderated', page=page, flags=flags,
                          search_form=None,
                          point_types=amo.REVIEWED_AMO,
                          motd_editable=motd_editable))
Exemplo n.º 15
0
    def __init__(self, *args, **kw):
        self.addon = kw.pop('addon')
        self.request = kw.pop('request', None)
        super(BaseCategoryFormSet, self).__init__(*args, **kw)
        self.initial = []
        apps = sorted(self.addon.compatible_apps.keys(), key=lambda x: x.id)

        # Drop any apps that don't have appropriate categories.
        qs = Category.objects.filter(type=self.addon.type)
        app_cats = {k: list(v) for k, v in sorted_groupby(qs, 'application')}
        for app in list(apps):
            if app and not app_cats.get(app.id):
                apps.remove(app)
        if not app_cats:
            apps = []

        for app in apps:
            cats = self.addon.app_categories.get(app, [])
            self.initial.append({'categories': [c.id for c in cats]})

        for app, form in zip(apps, self.forms):
            key = app.id if app else None
            form.request = self.request
            form.initial['application'] = key
            form.app = app
            cats = sorted(app_cats[key], key=lambda x: x.name)
            form.fields['categories'].choices = [(c.id, c.name) for c in cats]

            # If this add-on is featured for this application, category
            # changes are forbidden.
            if not acl.action_allowed(self.request,
                                      amo.permissions.ADDONS_EDIT):
                form.disabled = (app and self.addon.is_featured(app))
Exemplo n.º 16
0
def queue_tabnav_themes_interactive(context):
    """Tabnav for the interactive shiny theme queues."""
    tabs = []
    if acl.action_allowed(context['request'], amo.permissions.THEMES_REVIEW):
        tabs.append((
            'editors.themes.queue_themes', 'pending', ugettext('Pending'),
        ))
    if acl.action_allowed(context['request'],
                          amo.permissions.THEME_ADMIN_TOOLS_VIEW):
        tabs.append((
            'editors.themes.queue_flagged', 'flagged', ugettext('Flagged'),
        ))
        tabs.append((
            'editors.themes.queue_rereview', 'rereview', ugettext('Updates'),
        ))
    return tabs
Exemplo n.º 17
0
def queue_moderated(request):
    # In addition to other checks, this only show reviews for public and
    # listed add-ons. Unlisted add-ons typically won't have reviews anyway
    # but they might if their status ever gets changed.
    rf = (Review.objects.exclude(Q(addon__isnull=True) |
                                 Q(addon__is_listed=False) |
                                 Q(reviewflag__isnull=True))
                        .filter(editorreview=1,
                                addon__status__in=amo.LISTED_STATUSES)
                        .order_by('reviewflag__created'))

    page = paginate(request, rf, per_page=20)
    motd_editable = acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit')

    flags = dict(ReviewFlag.FLAGS)

    reviews_formset = ReviewFlagFormSet(request.POST or None,
                                        queryset=page.object_list,
                                        request=request)

    if request.method == 'POST':
        if reviews_formset.is_valid():
            reviews_formset.save()
        else:
            amo.messages.error(
                request, ' '.join(e.as_text() or _('An unknown error occurred')
                                  for e in reviews_formset.errors))
        return redirect(reverse('editors.queue_moderated'))

    return render(request, 'editors/queue.html',
                  context(request, reviews_formset=reviews_formset,
                          tab='moderated', page=page, flags=flags,
                          search_form=None,
                          point_types=amo.REVIEWED_AMO,
                          motd_editable=motd_editable))
Exemplo n.º 18
0
 def get_actions(self, request):
     actions = super().get_actions(request)
     if not acl.action_allowed(request, amo.permissions.ABUSEREPORTS_EDIT):
         # You need AbuseReports:Edit for the extra actions.
         actions.pop('mark_as_valid')
         actions.pop('mark_as_suspicious')
     return actions
Exemplo n.º 19
0
 def change_view(self, request, object_id, form_url='', extra_context=None):
     extra_context = extra_context or {}
     extra_context['can_ban'] = acl.action_allowed(
         request, amo.permissions.USERS_EDIT)
     return super(UserAdmin, self).change_view(
         request, object_id, form_url, extra_context=extra_context,
     )
Exemplo n.º 20
0
def queue_tabnav_themes(context):
    """Similar to queue_tabnav, but for themes."""
    tabs = []
    if acl.action_allowed(context['request'], 'Personas', 'Review'):
        tabs.append((
            'editors.themes.list', 'pending_themes', _('Pending'),
        ))
    if acl.action_allowed(context['request'], 'SeniorPersonasTools', 'View'):
        tabs.append((
            'editors.themes.list_flagged', 'flagged_themes', _('Flagged'),
        ))
        tabs.append((
            'editors.themes.list_rereview', 'rereview_themes',
            _('Updates'),
        ))
    return tabs
Exemplo n.º 21
0
def review_list(request, addon, review_id=None, user_id=None):
    qs = Rating.without_replies.all().filter(
        addon=addon).order_by('-created')

    ctx = {'addon': addon,
           'grouped_ratings': GroupedRating.get(addon.id)}

    ctx['form'] = forms.RatingForm(None)
    is_admin = acl.action_allowed(request, amo.permissions.ADDONS_EDIT)

    if review_id is not None:
        ctx['page'] = 'detail'
        # If this is a dev reply, find the first msg for context.
        review = get_object_or_404(Rating.objects.all(), pk=review_id)
        if review.reply_to_id:
            review_id = review.reply_to_id
            ctx['reply'] = review
        qs = qs.filter(pk=review_id)
    elif user_id is not None:
        ctx['page'] = 'user'
        qs = qs.filter(user=user_id)
        if not qs:
            raise http.Http404()
    else:
        ctx['page'] = 'list'
        qs = qs.filter(is_latest=True)
        # Don't filter out empty reviews for admins.
        if not is_admin:
            # But otherwise, filter out everyone elses empty reviews.
            user_filter = (Q(user=request.user.pk)
                           if request.user.is_authenticated() else Q())
            qs = qs.filter(~Q(body=None) | user_filter)

    ctx['reviews'] = reviews = paginate(request, qs)
    ctx['replies'] = Rating.get_replies(reviews.object_list)
    if request.user.is_authenticated():
        ctx['review_perms'] = {
            'is_admin': is_admin,
            'is_reviewer': acl.action_allowed(
                request, amo.permissions.RATINGS_MODERATE),
            'is_author': acl.check_addon_ownership(request, addon, viewer=True,
                                                   dev=True, support=True),
        }
        ctx['flags'] = get_flags(request, reviews.object_list)
    else:
        ctx['review_perms'] = {}
    return render(request, 'ratings/review_list.html', ctx)
Exemplo n.º 22
0
    def get_actions(self, request, addon):
        actions = SortedDict()
        if request is None:
            # If request is not set, it means we are just (ab)using the
            # ReviewHelper for its `handler` attribute and we don't care about
            # the actions.
            return actions
        labels, details = self._review_actions()
        reviewable_because_complete = addon.status not in (
            amo.STATUS_NULL, amo.STATUS_DELETED)
        reviewable_because_admin = (
            not addon.admin_review or
            acl.action_allowed(request, 'ReviewerAdminTools', 'View'))
        reviewable_because_submission_time = (
            not is_limited_reviewer(request) or
            (addon.latest_version is not None and
                addon.latest_version.nomination is not None and
                (datetime.datetime.now() - addon.latest_version.nomination >=
                    datetime.timedelta(hours=REVIEW_LIMITED_DELAY_HOURS))))
        reviewable_because_pending = addon.latest_version is not None and (
            len(addon.latest_version.is_unreviewed) > 0 or
            addon.status == amo.STATUS_LITE_AND_NOMINATED)
        if (reviewable_because_complete and
                reviewable_because_admin and
                reviewable_because_submission_time and
                reviewable_because_pending):
            if self.review_type != 'preliminary':
                if addon.is_listed:
                    label = _lazy('Push to public')
                else:
                    label = _lazy('Grant full review')
                actions['public'] = {'method': self.handler.process_public,
                                     'minimal': False,
                                     'label': label}
            # An unlisted sideload add-on, which requests a full review, cannot
            # be granted a preliminary review.
            prelim_allowed = not waffle.flag_is_active(
                request, 'no-prelim-review') and addon.is_listed
            if prelim_allowed or self.review_type == 'preliminary':
                actions['prelim'] = {
                    'method': self.handler.process_preliminary,
                    'label': labels['prelim'],
                    'minimal': False}
            actions['reject'] = {'method': self.handler.process_sandbox,
                                 'label': _lazy('Reject'),
                                 'minimal': False}
        actions['info'] = {'method': self.handler.request_information,
                           'label': _lazy('Request more information'),
                           'minimal': True}
        actions['super'] = {'method': self.handler.process_super_review,
                            'label': _lazy('Request super-review'),
                            'minimal': True}
        actions['comment'] = {'method': self.handler.process_comment,
                              'label': _lazy('Comment'),
                              'minimal': True}
        for k, v in actions.items():
            v['details'] = details.get(k)

        return actions
Exemplo n.º 23
0
    def wrapper(request, addon_id=None, *args, **kw):
        # Admins can see stats for every add-on regardless of its status.
        if acl.action_allowed(request, permissions.STATS_VIEW):
            qs = Addon.objects.all
        else:
            qs = Addon.objects.valid

        return addon_view(f, qs)(request, addon_id=addon_id, *args, **kw)
Exemplo n.º 24
0
def _view_on_get(request):
    """Return True if the user can access this page.

    If the user is in a group with rule 'ReviewerTools:View' and the request is
    a GET request, they are allowed to view.
    """
    return (request.method == 'GET' and
            acl.action_allowed(request, 'ReviewerTools', 'View'))
Exemplo n.º 25
0
    def has_object_permission(self, request, view, obj):
        can_access_because_viewer = (
            request.method in SAFE_METHODS and
            acl.action_allowed(request, permissions.REVIEWER_TOOLS_VIEW))
        can_access_because_listed_reviewer = (
            obj.has_listed_versions() and acl.is_reviewer(request, obj))

        return can_access_because_viewer or can_access_because_listed_reviewer
Exemplo n.º 26
0
 def _clean_upload(self):
     if not (self.cleaned_data['upload'].valid or
             self.cleaned_data['upload'].validation_timeout or
             self.cleaned_data['admin_override_validation'] and
             acl.action_allowed(self.request, 'ReviewerAdminTools',
                                'View')):
         raise forms.ValidationError(_(u'There was an error with your '
                                       u'upload. Please try again.'))
Exemplo n.º 27
0
def _view_on_get(request):
    """Return True if the user can access this page.

    If the user is in a group with rule 'ReviewerTools:View' and the request is
    a GET request, they are allowed to view.
    """
    return (request.method == 'GET' and
            acl.action_allowed(request, amo.permissions.REVIEWER_TOOLS_VIEW))
Exemplo n.º 28
0
def save_motd(request):
    if not acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit'):
        raise PermissionDenied
    form = forms.MOTDForm(request.POST)
    if form.is_valid():
        set_config('editors_review_motd', form.cleaned_data['motd'])
        return redirect(reverse('editors.motd'))
    data = context(request, form=form)
    return render(request, 'editors/motd.html', data)
Exemplo n.º 29
0
 def _clean_upload(self):
     if not (self.cleaned_data['upload'].valid or
             self.cleaned_data['upload'].validation_timeout or
             self.cleaned_data['admin_override_validation'] and
             acl.action_allowed(self.request,
                                amo.permissions.REVIEWER_ADMIN_TOOLS_VIEW)):
         raise forms.ValidationError(
             ugettext(u'There was an error with your upload. '
                      u'Please try again.'))
Exemplo n.º 30
0
 def wrapper(request, *args, **kw):
     admin = (action_allowed(request, 'Admin', '%') or
              action_allowed(request, 'AdminTools', 'View'))
     # Yes, the "is True" is here on purpose... because this decorator
     # takes optional arguments, but doesn't do it properly (so if
     # you're not giving it arguments, it takes the decorated function
     # as the first argument, and then "reviewers" is truthy.
     if reviewers is True:
         admin = (
             admin or
             action_allowed(request, 'ReviewerAdminTools', 'View'))
     if theme_reviewers is True:
         admin = (
             admin or
             action_allowed(request, 'SeniorPersonasTools', 'View'))
     if admin:
         return f(request, *args, **kw)
     raise PermissionDenied
Exemplo n.º 31
0
def _queue(request, TableObj, tab, qs=None, unlisted=False):
    if qs is None:
        qs = TableObj.Meta.model.objects.all()

    if is_limited_reviewer(request):
        qs = qs.having('waiting_time_hours >=', REVIEW_LIMITED_DELAY_HOURS)

    if request.GET:
        search_form = forms.QueueSearchForm(request.GET)
        if search_form.is_valid():
            qs = search_form.filter_qs(qs)
    else:
        search_form = forms.QueueSearchForm()
    admin_reviewer = is_admin_reviewer(request)
    if not admin_reviewer and not search_form.data.get('searching'):
        qs = exclude_admin_only_addons(qs)

    motd_editable = acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit')
    order_by = request.GET.get('sort', TableObj.default_order_by())
    if hasattr(TableObj, 'translate_sort_cols'):
        order_by = TableObj.translate_sort_cols(order_by)
    table = TableObj(data=qs, order_by=order_by)
    default = 100
    per_page = request.GET.get('per_page', default)
    try:
        per_page = int(per_page)
    except ValueError:
        per_page = default
    if per_page <= 0 or per_page > 200:
        per_page = default
    page = paginate(request, table.rows, per_page=per_page)
    table.set_page(page)
    return render(request, 'editors/queue.html',
                  context(request, table=table, page=page, tab=tab,
                          search_form=search_form,
                          point_types=amo.REVIEWED_AMO,
                          unlisted=unlisted,
                          motd_editable=motd_editable))
Exemplo n.º 32
0
def queue_moderated(request):
    # In addition to other checks, this only show reviews for public and
    # listed add-ons. Unlisted add-ons typically won't have reviews anyway
    # but they might if their status ever gets changed.
    rf = (Review.objects.exclude(
        Q(addon__isnull=True) | Q(addon__is_listed=False)
        | Q(reviewflag__isnull=True)).filter(
            editorreview=1, addon__status__in=amo.LISTED_STATUSES).order_by(
                'reviewflag__created'))

    page = paginate(request, rf, per_page=20)
    motd_editable = acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit')

    flags = dict(ReviewFlag.FLAGS)

    reviews_formset = ReviewFlagFormSet(request.POST or None,
                                        queryset=page.object_list,
                                        request=request)

    if request.method == 'POST':
        if reviews_formset.is_valid():
            reviews_formset.save()
        else:
            amo.messages.error(
                request, ' '.join(e.as_text() or _('An unknown error occurred')
                                  for e in reviews_formset.errors))
        return redirect(reverse('editors.queue_moderated'))

    return render(
        request, 'editors/queue.html',
        context(request,
                reviews_formset=reviews_formset,
                tab='moderated',
                page=page,
                flags=flags,
                search_form=None,
                point_types=amo.REVIEWED_AMO,
                motd_editable=motd_editable))
Exemplo n.º 33
0
def reviewlog(request):
    data = request.GET.copy()

    motd_editable = acl.action_allowed(
        request, amo.permissions.ADDON_REVIEWER_MOTD_EDIT)

    if not data.get('start') and not data.get('end'):
        today = date.today()
        data['start'] = date(today.year, today.month, 1)

    form = forms.ReviewLogForm(data)

    approvals = ActivityLog.objects.review_log()
    if not acl.check_unlisted_addons_reviewer(request):
        # Display logs related to unlisted versions only to senior reviewers.
        list_channel = amo.RELEASE_CHANNEL_LISTED
        approvals = approvals.filter(versionlog__version__channel=list_channel)

    if form.is_valid():
        data = form.cleaned_data
        if data['start']:
            approvals = approvals.filter(created__gte=data['start'])
        if data['end']:
            approvals = approvals.filter(created__lt=data['end'])
        if data['search']:
            term = data['search']
            approvals = approvals.filter(
                Q(commentlog__comments__icontains=term)
                | Q(addonlog__addon__name__localized_string__icontains=term)
                | Q(user__display_name__icontains=term)
                | Q(user__username__icontains=term)).distinct()

    pager = amo.utils.paginate(request, approvals, 50)
    data = context(request,
                   form=form,
                   pager=pager,
                   motd_editable=motd_editable)
    return render(request, 'reviewers/reviewlog.html', data)
Exemplo n.º 34
0
def download_source(request, version_id):
    """
    Download source code for a given version_id.

    Requires developer of the add-on or admin reviewer permission. If the
    version or add-on is deleted, developers can't access.

    If the version source code wasn't provided, but the user had the right
    permissions, a 404 is raised.
    """
    # Include deleted versions in the queryset, we'll check for that below.
    version = get_object_or_404(Version.unfiltered, pk=version_id)
    addon = version.addon

    # Channel doesn't matter, source code is only available to admin reviewers
    # or developers of the add-on. If the add-on, version or file is deleted or
    # disabled, then only admins can access.
    has_permission = acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN)

    if (addon.status != amo.STATUS_DISABLED
            and not version.files.filter(status=amo.STATUS_DISABLED).exists()
            and not version.deleted and not addon.is_deleted):
        # Don't rely on 'admin' parameter for check_addon_ownership(), it
        # doesn't check the permission we want to check.
        has_permission = has_permission or acl.check_addon_ownership(
            request, addon, admin=False, dev=True)
    if not has_permission:
        raise http.Http404()

    response = HttpResponseXSendFile(request, version.source.path)
    path = version.source.path
    if not isinstance(path, str):
        path = path.decode('utf8')
    name = os.path.basename(path.replace('"', ''))
    disposition = 'attachment; filename="{0}"'.format(name).encode('utf8')
    response['Content-Disposition'] = disposition
    response['Access-Control-Allow-Origin'] = '*'
    return response
Exemplo n.º 35
0
def check_stats_permission(request, addon, beta):
    """
    Check if user is allowed to view stats/beta stats for ``addon``.

    Raises PermissionDenied if user is not allowed.

    Raises Http404 if user cannot access the beta mode (only if enabled).
    """
    user = request.user
    user_cannot_access_beta = not user.is_authenticated or (
        user.is_authenticated
        and not (user.email.endswith('@mozilla.com')
                 or waffle.flag_is_active(request, 'beta-stats')))
    addon_has_no_beta = addon.type not in amo.ADDON_TYPES_WITH_STATS

    if beta and (user_cannot_access_beta or addon_has_no_beta):
        raise http.Http404

    can_view = user.is_authenticated and (
        addon.has_author(user)
        or acl.action_allowed(request, amo.permissions.STATS_VIEW))
    if not can_view:
        raise PermissionDenied
Exemplo n.º 36
0
    def change_view(self, request, object_id, form_url='', extra_context=None):
        extra_context = extra_context or {}
        extra_context['has_users_edit_permission'] = acl.action_allowed(
            request, amo.permissions.USERS_EDIT)

        lookup_field = UserProfile.get_lookup_field(object_id)
        if lookup_field != 'pk':
            try:
                if lookup_field == 'email':
                    user = UserProfile.objects.get(email=object_id)
            except UserProfile.DoesNotExist:
                raise http.Http404
            url = request.path.replace(object_id, str(user.id), 1)
            if request.GET:
                url += '?' + request.GET.urlencode()
            return http.HttpResponsePermanentRedirect(url)

        return super(UserAdmin, self).change_view(
            request,
            object_id,
            form_url,
            extra_context=extra_context,
        )
Exemplo n.º 37
0
    def __init__(self, *args, **kw):
        self.addon = kw.pop('addon')
        self.request = kw.pop('request', None)
        super(SingleCategoryForm, self).__init__(*args, **kw)

        form = self.fields['category']
        # Hack because we know this is only used for Static Themes that only
        # support Firefox.  Hoping to unify per-app categories in the meantime.
        app = amo.FIREFOX
        sorted_cats = sorted(CATEGORIES[app.id][self.addon.type].items(),
                             key=lambda (slug, cat): slug)
        choices = [
            (c.id, c.name) for _, c in sorted_cats]
        form.choices = choices  # sorted(choices, key=lambda x: x.name)

        initials = self.addon.app_categories.get(app.id, [])
        if len(initials) > 0:
            form.initial = initials[0]

        # If this add-on is featured for this application, category changes are
        # forbidden.
        if not acl.action_allowed(self.request, amo.permissions.ADDONS_EDIT):
            form.disabled = (app and self.addon.is_featured(app))
Exemplo n.º 38
0
def reply(request, addon, review_id):
    is_admin = acl.action_allowed(request, amo.permissions.ADDONS_EDIT)
    is_author = acl.check_addon_ownership(request, addon, dev=True)
    if not (is_admin or is_author):
        raise PermissionDenied

    rating = get_object_or_404(Rating.objects, pk=review_id, addon=addon)
    form = forms.RatingReplyForm(request.POST or None)
    if request.method == 'POST' and form.is_valid():
        kwargs = {
            'reply_to': rating,
            'addon': addon,
            'defaults': _review_details(request, addon, form)
        }
        reply, created = Rating.unfiltered.update_or_create(**kwargs)
        return redirect(jinja_helpers.url(
            'addons.ratings.detail', addon.slug, review_id))
    ctx = {
        'review': rating,
        'form': form,
        'addon': addon
    }
    return render(request, 'ratings/reply.html', ctx)
Exemplo n.º 39
0
def policy_viewer(request, addon, eula_or_privacy, page_title, long_title):
    if not eula_or_privacy:
        raise http.Http404
    channel_text = request.GET.get('channel')
    channel, content_review_only = determine_channel(channel_text)
    # It's a read-only view so we can bypass the specific permissions checks
    # if we have ReviewerTools:View.
    bypass_more_specific_permissions_because_read_only = (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)

    review_url = reverse('reviewers.review',
                         args=(channel_text or 'listed',
                               addon.slug if addon.slug else addon.pk))
    return render(
        request, 'reviewers/policy_view.html', {
            'addon': addon,
            'review_url': review_url,
            'content': eula_or_privacy,
            'page_title': page_title,
            'long_title': long_title
        })
Exemplo n.º 40
0
    def handle_false_positive(self, request, pk, *args, **kwargs):
        is_admin = acl.action_allowed(
            request, amo.permissions.ADMIN_SCANNERS_RESULTS_EDIT)
        if not is_admin or request.method != "POST":
            raise Http404

        result = self.get_object(request, pk)
        result.update(state=FALSE_POSITIVE)

        messages.add_message(
            request,
            messages.INFO,
            'Scanner result {} has been marked as false positive.'.format(pk),
        )

        title = 'False positive report for ScannerResult {}'.format(pk)
        body = render_to_string('admin/false_positive_report.md', {
            'result': result,
            'YARA': YARA
        })
        labels = ','.join([
            # Default label added to all issues
            'false positive report'
        ] + [
            'rule: {}'.format(rule.name)
            for rule in result.matched_rules.all()
        ])

        return redirect('https://github.com/{}/issues/new?{}'.format(
            result.get_git_repository(),
            urlencode({
                'title': title,
                'body': body,
                'labels': labels
            }),
        ))
Exemplo n.º 41
0
def spam(request):
    if not acl.action_allowed(request, 'Spam', 'Flag'):
        raise PermissionDenied
    spam = Spam()

    if request.method == 'POST':
        review = Review.objects.get(pk=request.POST['review'])
        if 'del_review' in request.POST:
            log.info('SPAM: %s' % review.id)
            delete(request, request.POST['addon'], review.id)
            messages.success(request, 'Deleted that review.')
        elif 'del_user' in request.POST:
            user = review.user
            log.info('SPAMMER: %s deleted %s' %
                     (request.user.username, user.username))
            if not user.is_developer:
                Review.objects.filter(user=user).delete()
                user.anonymize()
            messages.success(request, 'Deleted that dirty spammer.')

        for reason in spam.reasons():
            spam.redis.srem(reason, review.id)
        return http.HttpResponseRedirect(request.path)

    buckets = {}
    for reason in spam.reasons():
        ids = spam.redis.smembers(reason)
        key = reason.split(':')[-1]
        buckets[key] = Review.objects.no_cache().filter(id__in=ids)
    reviews = dict((review.addon_id, review)
                   for bucket in buckets.values()
                   for review in bucket)
    for addon in Addon.objects.no_cache().filter(id__in=reviews):
        reviews[addon.id].addon = addon
    return render(request, 'reviews/spam.html',
                  dict(buckets=buckets, review_perms=dict(is_admin=True)))
Exemplo n.º 42
0
def review(request, addon):
    if not addon.is_listed and not acl.check_unlisted_addons_reviewer(request):
        raise http.Http404

    version = addon.find_latest_version_including_rejected()

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

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

    is_admin = acl.action_allowed(request, 'Addons', 'Edit')

    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, _('Review successfully processed.'))
        clear_review_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.files.all():
            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(
            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 from.
    actions_minimal = [k for (k, a) in actions if not a.get('minimal')]

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

    class PseudoVersion(object):
        def __init__(self):
            self.all_activity = []

        all_files = ()
        approvalnotes = None
        compatible_apps_ordered = ()
        releasenotes = None
        status = 'Deleted'
        deleted = True
        channel = amo.RELEASE_CHANNEL_LISTED

        @property
        def created(self):
            return self.all_activity[0].created

        @property
        def version(self):
            return (self.all_activity[0].activity_log.details.get(
                'version', '[deleted]'))

    # Grab review history for deleted versions of this add-on
    # Version are soft-deleted now but we need this for older deletions.
    comments = (CommentLog.objects.filter(
        activity_log__action__in=amo.LOG_REVIEW_QUEUE,
        activity_log__versionlog=None,
        activity_log__addonlog__addon=addon).order_by(
            'created').select_related('activity_log'))

    comment_versions = defaultdict(PseudoVersion)
    for c in comments:
        c.version = c.activity_log.details.get('version', c.created)
        comment_versions[c.version].all_activity.append(c)

    all_versions = comment_versions.values()
    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

    try:
        flags = ViewQueue.objects.get(id=addon.id).flags
    except ViewQueue.DoesNotExist:
        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,
                  whiteboard_form=forms.WhiteboardForm(instance=addon),
                  user_changes=user_changes_log,
                  unlisted=not addon.is_listed)

    return render(request, 'editors/review.html', ctx)
Exemplo n.º 43
0
 def get_tags(self, addon):
     if acl.action_allowed(self.request, amo.permissions.ADDONS_EDIT):
         return list(addon.tags.values_list('tag_text', flat=True))
     else:
         return list(addon.tags.filter(restricted=False)
                     .values_list('tag_text', flat=True))
Exemplo n.º 44
0
def queue_tabnav(context):
    """Returns tuple of tab navigation for the queue pages.

    Each tuple contains three elements: (tab_code, page_url, tab_text)
    """
    counts = context['queue_counts']
    request = context['request']
    listed = not context.get('unlisted')

    if listed:
        tabnav = []
        if acl.action_allowed(
                request, amo.permissions.ADDONS_RECOMMENDED_REVIEW):
            tabnav.append(
                ('recommended', 'queue_recommended',
                 ugettext('Recommended ({0})').format(counts['recommended'])),
            )
        if acl.action_allowed(request, amo.permissions.ADDONS_REVIEW):
            new_text = ugettext('Other Pending Review ({0})')
            tabnav.extend((
                ('extension', 'queue_extension',
                 '🛠️ ' + new_text.format(counts['extension'])),
            ))
            tabnav.append((
                'scanners',
                'queue_scanners',
                (ungettext(
                    'Flagged By Scanners ({0})',
                    'Flagged By Scanners ({0})',
                    counts['scanners']
                ) .format(counts['scanners'])),
            ))
            tabnav.append((
                'mad',
                'queue_mad',
                (ungettext(
                    'Flagged for Human Review ({0})',
                    'Flagged for Human Review ({0})',
                    counts['mad']
                ).format(counts['mad'])),
            ))
        if acl.action_allowed(request, amo.permissions.STATIC_THEMES_REVIEW):
            new_text = ugettext('New ({0})')
            update_text = ungettext(
                'Update ({0})', 'Updates ({0})', counts['theme_pending'])
            tabnav.extend((
                ('theme_nominated', 'queue_theme_nominated',
                 '🎨 ' + new_text.format(counts['theme_nominated'])),
                ('theme_pending', 'queue_theme_pending',
                 '🎨 ' + update_text.format(counts['theme_pending'])),
            ))
        if acl.action_allowed(request, amo.permissions.RATINGS_MODERATE):
            tabnav.append(
                ('moderated', 'queue_moderated',
                 (ungettext('Rating Review ({0})',
                            'Rating Reviews ({0})',
                            counts['moderated'])
                  .format(counts['moderated']))),
            )

        if acl.action_allowed(request, amo.permissions.ADDONS_POST_REVIEW):
            tabnav.append(
                ('auto_approved', 'queue_auto_approved',
                 (ungettext('Auto Approved ({0})',
                            'Auto Approved ({0})',
                            counts['auto_approved'])
                  .format(counts['auto_approved']))),
            )

        if acl.action_allowed(request, amo.permissions.ADDONS_CONTENT_REVIEW):
            tabnav.append(
                ('content_review', 'queue_content_review',
                 (ungettext('Content Review ({0})',
                            'Content Review ({0})',
                            counts['content_review'])
                  .format(counts['content_review']))),
            )

        if acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN):
            tabnav.append(
                ('expired_info_requests', 'queue_expired_info_requests',
                 (ungettext('Expired Info Request ({0})',
                            'Expired Info Requests ({0})',
                            counts['expired_info_requests'])
                  .format(counts['expired_info_requests']))),
            )
            tabnav.append(
                ('pending_rejection', 'queue_pending_rejection',
                 (ungettext('Pending Rejection ({0})',
                            'Pending Rejection ({0})',
                            counts['pending_rejection'])
                  .format(counts['pending_rejection']))),
            )
    else:
        tabnav = [
            ('all', 'unlisted_queue_all', ugettext('All Unlisted Add-ons'))
        ]

    return tabnav
Exemplo n.º 45
0
 def can_view_stats(self, request):
     if request and request.user:
         return (self.owned_by(request.user) or acl.action_allowed(
             request, amo.permissions.COLLECTION_STATS_VIEW))
     return False
Exemplo n.º 46
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 = gettext('Tools')
    is_reviewer = False

    # We're using `getattr` here because `request.user` can be missing,
    # e.g in case of a 500-server error.
    if getattr(request, 'user', AnonymousUser()).is_authenticated:
        is_reviewer = acl.is_user_any_kind_of_reviewer(request.user)

        account_links.append({
            'text': gettext('My Profile'),
            'href': request.user.get_url_path()
        })

        account_links.append({
            'text': gettext('Account Settings'),
            'href': reverse('users.edit')
        })
        account_links.append({
            'text': gettext('My Collections'),
            'href': reverse('collections.list')
        })

        account_links.append({
            'text':
            gettext('Log out'),
            'href':
            reverse('devhub.logout') + '?to=' + quote(request.path),
        })

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

        if is_reviewer:
            tools_links.append({
                'text': gettext('Reviewer Tools'),
                'href': reverse('reviewers.dashboard'),
            })
        if acl.action_allowed(request, amo.permissions.ANY_ADMIN):
            tools_links.append({
                'text': gettext('Admin Tools'),
                'href': reverse('admin: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,
        'is_reviewer': is_reviewer,
    })
    return context
Exemplo n.º 47
0
    def get_actions(self, request, addon):
        actions = SortedDict()
        if request is None:
            # If request is not set, it means we are just (ab)using the
            # ReviewHelper for its `handler` attribute and we don't care about
            # the actions.
            return actions
        labels, details = self._review_actions()
        reviewable_because_complete = addon.status not in (amo.STATUS_NULL,
                                                           amo.STATUS_DELETED)
        reviewable_because_admin = (not addon.admin_review
                                    or acl.action_allowed(
                                        request, 'ReviewerAdminTools', 'View'))
        reviewable_because_submission_time = (
            not is_limited_reviewer(request)
            or (addon.latest_version is not None
                and addon.latest_version.nomination is not None and
                (datetime.datetime.now() - addon.latest_version.nomination >=
                 datetime.timedelta(hours=REVIEW_LIMITED_DELAY_HOURS))))
        reviewable_because_pending = addon.latest_version is not None and len(
            addon.latest_version.is_unreviewed) > 0
        if (reviewable_because_complete and reviewable_because_admin
                and reviewable_because_submission_time
                and reviewable_because_pending):
            if self.review_type != 'preliminary':
                if addon.is_listed:
                    label = _lazy('Push to public')
                else:
                    label = _lazy('Grant full review')
                actions['public'] = {
                    'method': self.handler.process_public,
                    'minimal': False,
                    'label': label
                }
            # An unlisted sideload add-on, which requests a full review, cannot
            # be granted a preliminary review.
            if addon.is_listed or self.review_type == 'preliminary':
                actions['prelim'] = {
                    'method': self.handler.process_preliminary,
                    'label': labels['prelim'],
                    'minimal': False
                }
            actions['reject'] = {
                'method': self.handler.process_sandbox,
                'label': _lazy('Reject'),
                'minimal': False
            }
        actions['info'] = {
            'method': self.handler.request_information,
            'label': _lazy('Request more information'),
            'minimal': True
        }
        actions['super'] = {
            'method': self.handler.process_super_review,
            'label': _lazy('Request super-review'),
            'minimal': True
        }
        actions['comment'] = {
            'method': self.handler.process_comment,
            'label': _lazy('Comment'),
            'minimal': True
        }
        for k, v in actions.items():
            v['details'] = details.get(k)

        return actions
Exemplo n.º 48
0
def queue_tabnav(context):
    """Returns tuple of tab navigation for the queue pages.

    Each tuple contains three elements: (tab_code, page_url, tab_text)
    """
    request = context['request']
    listed = not context.get('unlisted')

    if listed:
        tabnav = []
        if acl.action_allowed(request,
                              amo.permissions.ADDONS_RECOMMENDED_REVIEW):
            tabnav.append(
                ('recommended', 'queue_recommended', gettext('Recommended')))
        if acl.action_allowed(request, amo.permissions.ADDONS_REVIEW):
            tabnav.append((
                'extension',
                'queue_extension',
                '🛠️ ' + gettext('Other Pending Review'),
            ))
            tabnav.append(
                ('scanners', 'queue_scanners', gettext('Flagged By Scanners')))
            tabnav.append(
                ('mad', 'queue_mad', gettext('Flagged for Human Review')))
        if acl.action_allowed(request, amo.permissions.STATIC_THEMES_REVIEW):
            tabnav.extend((
                (
                    'theme_nominated',
                    'queue_theme_nominated',
                    '🎨 ' + gettext('New'),
                ),
                (
                    'theme_pending',
                    'queue_theme_pending',
                    '🎨 ' + gettext('Updates'),
                ),
            ))
        if acl.action_allowed(request, amo.permissions.RATINGS_MODERATE):
            tabnav.append(
                ('moderated', 'queue_moderated', gettext('Rating Reviews')))

        if acl.action_allowed(request, amo.permissions.ADDONS_REVIEW):
            tabnav.append(('auto_approved', 'queue_auto_approved',
                           gettext('Auto Approved')))

        if acl.action_allowed(request, amo.permissions.ADDONS_CONTENT_REVIEW):
            tabnav.append(('content_review', 'queue_content_review',
                           gettext('Content Review')))

        if acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN):
            tabnav.append((
                'pending_rejection',
                'queue_pending_rejection',
                gettext('Pending Rejection'),
            ))
    else:
        tabnav = [
            ('all', 'unlisted_queue_all', gettext('All Unlisted Add-ons')),
            (
                'pending_manual_approval',
                'unlisted_queue_pending_manual_approval',
                gettext('Unlisted Add-ons Pending Manual Approval'),
            ),
        ]

    return tabnav
Exemplo n.º 49
0
def _get_themes(request, reviewer, flagged=False, rereview=False):
    """Check out themes.

    :param flagged: Flagged themes (amo.STATUS_REVIEW_PENDING)
    :param rereview: Re-uploaded themes (RereviewQueueTheme)

    """
    num = 0
    themes = []
    locks = []

    status = (amo.STATUS_REVIEW_PENDING if flagged else
              amo.STATUS_PUBLIC if rereview else amo.STATUS_PENDING)

    if rereview:
        # Rereview themes.
        num, themes, locks = _get_rereview_themes(reviewer)
    else:
        # Pending and flagged themes.
        locks = ThemeLock.objects.no_cache().filter(
            reviewer=reviewer, theme__addon__status=status)
        num, themes = _calc_num_themes_checkout(locks)
        if themes:
            return themes
        themes = Persona.objects.no_cache().filter(addon__status=status,
                                                   themelock=None)

    # Don't allow self-reviews.
    if (not settings.ALLOW_SELF_REVIEWS
            and not acl.action_allowed(request, amo.permissions.ADMIN)):
        if rereview:
            themes = themes.exclude(theme__addon__addonuser__user=reviewer)
        else:
            themes = themes.exclude(addon__addonuser__user=reviewer)

    # Check out themes by setting lock.
    themes = list(themes)[:num]
    expiry = get_updated_expiry()
    for theme in themes:
        ThemeLock.objects.create(theme=_rereview_to_theme(rereview, theme),
                                 reviewer=reviewer,
                                 expiry=expiry)

    # Empty pool? Go look for some expired locks.
    if not themes:
        expired_locks = ThemeLock.objects.filter(
            expiry__lte=datetime.datetime.now(),
            theme__addon__status=status)[:rvw.THEME_INITIAL_LOCKS]
        # Steal expired locks.
        for lock in expired_locks:
            lock.reviewer = reviewer
            lock.expiry = expiry
            lock.save()
        if expired_locks:
            locks = expired_locks

    if rereview:
        return (RereviewQueueTheme.objects.no_cache().filter(
            theme__themelock__reviewer=reviewer).exclude(
                theme__addon__status=amo.STATUS_REJECTED))

    # New theme locks may have been created, grab all reviewer's themes again.
    return [lock.theme for lock in locks]
Exemplo n.º 50
0
def is_admin_reviewer(request):
    return acl.action_allowed(request, 'ReviewerAdminTools', 'View')
Exemplo n.º 51
0
 def wrapper(request, *args, **kw):
     view_on_get = _view_on_get(request)
     if view_on_get or acl.action_allowed(request, permission):
         return f(request, *args, **kw)
     else:
         raise PermissionDenied
Exemplo n.º 52
0
def themes_single(request, slug):
    """
    Like a detail page, manually review a single theme if it is pending
    and isn't locked.
    """
    reviewer = request.user
    reviewable = True

    # Don't review an already reviewed theme.
    theme = get_object_or_404(Persona, addon__slug=slug)
    if (theme.addon.status != amo.STATUS_PENDING
            and not theme.rereviewqueuetheme_set.all()):
        reviewable = False

    if (not settings.ALLOW_SELF_REVIEWS
            and not acl.action_allowed(request, amo.permissions.ADMIN)
            and theme.addon.has_author(request.user)):
        reviewable = False
    else:
        # Don't review a locked theme (that's not locked to self).
        try:
            lock = theme.themelock
            if (lock.reviewer.id != reviewer.id
                    and lock.expiry > datetime.datetime.now()):
                reviewable = False
            elif (lock.reviewer.id != reviewer.id
                  and lock.expiry < datetime.datetime.now()):
                # Steal expired lock.
                lock.reviewer = reviewer
                lock.expiry = get_updated_expiry()
                lock.save()
            else:
                # Update expiry.
                lock.expiry = get_updated_expiry()
                lock.save()
        except ThemeLock.DoesNotExist:
            # Create lock if not created.
            ThemeLock.objects.create(theme=theme,
                                     reviewer=reviewer,
                                     expiry=get_updated_expiry())

    ThemeReviewFormset = formset_factory(forms.ThemeReviewForm)
    formset = ThemeReviewFormset(initial=[{'theme': theme.id}])

    # Since we started the review on the single page, we want to return to the
    # single page rather than get shot back to the queue.
    request.session['theme_redirect_url'] = reverse('editors.themes.single',
                                                    args=[theme.addon.slug])

    rereview = (theme.rereviewqueuetheme_set.all()[0]
                if theme.rereviewqueuetheme_set.exists() else None)
    return render(
        request, 'editors/themes/single.html',
        context(
            **{
                'formset':
                formset,
                'theme':
                rereview if rereview else theme,
                'theme_formsets':
                zip([rereview if rereview else theme], formset),
                'theme_reviews':
                paginate(
                    request,
                    ActivityLog.objects.filter(
                        action=amo.LOG.THEME_REVIEW.id,
                        _arguments__contains=theme.addon.id)),
                'actions':
                get_actions_json(),
                'theme_count':
                1,
                'rereview':
                rereview,
                'reviewable':
                reviewable,
                'reject_reasons':
                rvw.THEME_REJECT_REASONS,
                'action_dict':
                rvw.REVIEW_ACTIONS,
                'tab':
                ('flagged' if theme.addon.status == amo.STATUS_REVIEW_PENDING
                 else 'rereview' if rereview else 'pending')
            }))
Exemplo n.º 53
0
 def wrapper(request, *args, **kw):
     if _view_on_get(request) or acl.action_allowed(
             request, permissions.RATINGS_MODERATE):
         return f(request, *args, **kw)
     raise PermissionDenied
Exemplo n.º 54
0
    def get_actions(self, request):
        actions = OrderedDict()
        if request is None:
            # If request is not set, it means we are just (ab)using the
            # ReviewHelper for its `handler` attribute and we don't care about
            # the actions.
            return actions

        # Conditions used below.
        is_post_reviewer = acl.action_allowed(
            request, amo.permissions.ADDONS_POST_REVIEW)
        is_unlisted_reviewer = acl.action_allowed(
            request, amo.permissions.ADDONS_REVIEW_UNLISTED)
        is_content_reviewer = acl.action_allowed(
            request, amo.permissions.ADDONS_CONTENT_REVIEW)
        is_admin_tools_viewer = acl.action_allowed(
            request, amo.permissions.REVIEWS_ADMIN)
        reviewable_because_complete = self.addon.status not in (
            amo.STATUS_NULL, amo.STATUS_DELETED)
        regular_addon_review_is_allowed = (
            not self.content_review_only
            and not self.addon.needs_admin_code_review
            and not self.addon.needs_admin_content_review
            and not self.addon.needs_admin_theme_review)
        regular_content_review_is_allowed = (
            self.content_review_only
            and not self.addon.needs_admin_content_review and
            (not self.addon.needs_admin_code_review or self.version.source))
        reviewable_because_not_reserved_for_admins_or_user_is_admin = (
            is_admin_tools_viewer
            or (self.version and (regular_addon_review_is_allowed
                                  or regular_content_review_is_allowed)))
        reviewable_because_submission_time = (
            not is_limited_reviewer(request)
            or (self.version and self.version.nomination is not None and
                (datetime.now() - self.version.nomination >=
                 timedelta(hours=amo.REVIEW_LIMITED_DELAY_HOURS))))
        reviewable_because_pending = (self.version
                                      and len(self.version.is_unreviewed) > 0)
        # Note: approval/content confirmation do not care about self.version,
        # only self.addon.current_version. This allows reviewers to approve
        # add-ons even when their latest submitted version is disabled for some
        # reason.
        was_auto_approved_and_user_can_post_review = (
            self.addon.current_version
            and self.addon.current_version.was_auto_approved
            and is_post_reviewer)
        was_auto_approved_and_user_can_content_review = (
            self.addon.current_version
            and self.addon.current_version.was_auto_approved
            and is_content_reviewer and self.content_review_only)
        is_unlisted_and_user_can_review_unlisted = (
            self.version
            and self.version.channel == amo.RELEASE_CHANNEL_UNLISTED
            and is_unlisted_reviewer)
        is_public_and_listed_and_user_can_post_review = (
            self.version and self.addon.status == amo.STATUS_PUBLIC
            and self.version.channel == amo.RELEASE_CHANNEL_LISTED
            and is_post_reviewer)
        is_public_and_listed_and_user_can_content_review = (
            self.version and self.addon.status == amo.STATUS_PUBLIC
            and self.version.channel == amo.RELEASE_CHANNEL_LISTED
            and is_content_reviewer and self.content_review_only)

        # Definitions for all actions.
        actions['public'] = {
            'method':
            self.handler.process_public,
            'minimal':
            False,
            'details':
            _('This will approve, sign, and publish this '
              'version. The comments will be sent to the '
              'developer.'),
            'label':
            _('Approve'),
            'available':
            (reviewable_because_complete
             and reviewable_because_not_reserved_for_admins_or_user_is_admin
             and reviewable_because_submission_time
             and reviewable_because_pending and not self.content_review_only)
        }
        actions['reject'] = {
            'method':
            self.handler.process_sandbox,
            'label':
            _('Reject'),
            'details':
            _('This will reject this version and remove it '
              'from the queue. The comments will be sent '
              'to the developer.'),
            'minimal':
            False,
            'available':
            actions['public']['available'],
        }
        actions['confirm_auto_approved'] = {
            'method':
            self.handler.confirm_auto_approved,
            'label':
            _('Confirm Approval'),
            'details':
            _('The latest public version of this add-on was '
              'automatically approved. This records your '
              'confirmation of the approval of that version, '
              'without notifying the developer.'),
            'minimal':
            True,
            'comments':
            False,
            'available':
            (reviewable_because_not_reserved_for_admins_or_user_is_admin
             and (was_auto_approved_and_user_can_post_review
                  or was_auto_approved_and_user_can_content_review
                  or is_unlisted_and_user_can_review_unlisted))
        }
        actions['reject_multiple_versions'] = {
            'method':
            self.handler.reject_multiple_versions,
            'label':
            _('Reject Multiple Versions'),
            'minimal':
            True,
            'versions':
            True,
            'details':
            _('This will reject the selected public '
              'versions. The comments will be sent to the '
              'developer.'),
            'available':
            (self.addon.type != amo.ADDON_STATICTHEME
             and reviewable_because_not_reserved_for_admins_or_user_is_admin
             and (is_public_and_listed_and_user_can_post_review
                  or is_public_and_listed_and_user_can_content_review))
        }
        actions['reply'] = {
            'method':
            self.handler.reviewer_reply,
            'label':
            _('Reviewer reply'),
            'details':
            _('This will send a message to the developer. '
              'You will be notified when they reply.'),
            'minimal':
            True,
            'available':
            self.version is not None,
        }
        actions['super'] = {
            'method':
            self.handler.process_super_review,
            'label':
            _('Request super-review'),
            'details':
            _('If you have concerns about this add-on that '
              'an admin reviewer should look into, enter '
              'your comments in the area below. They will '
              'not be sent to the developer.'),
            'minimal':
            True,
            'available':
            self.version is not None,
        }
        actions['comment'] = {
            'method':
            self.handler.process_comment,
            'label':
            _('Comment'),
            'details':
            _('Make a comment on this version. The developer '
              'won\'t be able to see this.'),
            'minimal':
            True,
            'available':
            True,
        }

        # Small tweaks to labels and descriptions when in content review mode.
        if self.content_review_only:
            if 'confirm_auto_approved' in actions:
                actions['confirm_auto_approved'].update({
                    'label':
                    _('Approve Content'),
                    'details':
                    _('This records your approbation of the '
                      'content of the latest public version, '
                      'without notifying the developer.'),
                })
        return OrderedDict(((key, action) for key, action in actions.items()
                            if action['available']))
Exemplo n.º 55
0
 def wrapper(request, *args, **kw):
     from olympia.access import acl
     for permission in permissions:
         if acl.action_allowed(request, permission):
             return f(request, *args, **kw)
     raise PermissionDenied
Exemplo n.º 56
0
    def get_actions(self, request):
        actions = SortedDict()
        if request is None:
            # If request is not set, it means we are just (ab)using the
            # ReviewHelper for its `handler` attribute and we don't care about
            # the actions.
            return actions
        reviewable_because_complete = self.addon.status not in (
            amo.STATUS_NULL, amo.STATUS_DELETED)
        reviewable_because_admin = (
            not self.addon.admin_review or acl.action_allowed(
                request, amo.permissions.REVIEWER_ADMIN_TOOLS_VIEW))
        reviewable_because_submission_time = (
            not is_limited_reviewer(request) or
            (self.version is not None and self.version.nomination is not None
             and (datetime.datetime.now() - self.version.nomination >=
                  datetime.timedelta(hours=REVIEW_LIMITED_DELAY_HOURS))))
        reviewable_because_pending = (self.version is not None
                                      and len(self.version.is_unreviewed) > 0)
        if (reviewable_because_complete and reviewable_because_admin
                and reviewable_because_submission_time
                and reviewable_because_pending):
            actions['public'] = {
                'method':
                self.handler.process_public,
                'minimal':
                False,
                'details':
                _('This will approve, sign, and publish this '
                  'version. The comments will be sent to the '
                  'developer.'),
                'label':
                _('Approve')
            }
            actions['reject'] = {
                'method':
                self.handler.process_sandbox,
                'label':
                _('Reject'),
                'details':
                _('This will reject this version and remove it '
                  'from the queue. The comments will be sent '
                  'to the developer.'),
                'minimal':
                False
            }
        if acl.action_allowed(request, amo.permissions.ADDONS_POST_REVIEW):
            # Post-reviewers have 2 extra actions depending on the state of
            # the add-on:
            # If the addon current version was auto-approved, they can confirm
            # the approval.
            if (self.addon.current_version
                    and self.addon.current_version.was_auto_approved):
                actions['confirm_auto_approved'] = {
                    'method':
                    self.handler.confirm_auto_approved,
                    'label':
                    _('Confirm Approval'),
                    'details':
                    _('The latest public version of this add-on '
                      'was automatically approved. This records '
                      'your confirmation of the approval, '
                      'without notifying the developer.'),
                    'minimal':
                    True,
                    'comments':
                    False,
                }
            # In any case, they can reject multiple versions in one action.
            actions['reject_multiple_versions'] = {
                'method':
                self.handler.reject_multiple_versions,
                'label':
                _('Reject Multiple Versions'),
                'minimal':
                True,
                'versions':
                True,
                'details':
                _('This will reject the selected versions. '
                  'The comments will be sent to the developer.'),
            }
        if self.version:
            actions['info'] = {
                'method':
                self.handler.request_information,
                'label':
                _('Reviewer reply'),
                'details':
                _('This will send a message to the developer. '
                  'You will be notified when they reply.'),
                'minimal':
                True
            }
            actions['super'] = {
                'method':
                self.handler.process_super_review,
                'label':
                _('Request super-review'),
                'details':
                _('If you have concerns about this add-on that '
                  'an admin reviewer should look into, enter '
                  'your comments in the area below. They will '
                  'not be sent to the developer.'),
                'minimal':
                True
            }
        actions['comment'] = {
            'method':
            self.handler.process_comment,
            'label':
            _('Comment'),
            'details':
            _('Make a comment on this version. The developer '
              'won\'t be able to see this.'),
            'minimal':
            True
        }

        return actions
Exemplo n.º 57
0
 def wrapper(request, *args, **kw):
     if (_view_on_get(request)
             or acl.action_allowed(request, permissions.ADDONS_REVIEW)
             or acl.check_static_theme_reviewer(request)):
         return f(request, *args, **kw)
     raise PermissionDenied
Exemplo n.º 58
0
def performance(request, user_id=False):
    user = request.user
    editors = _recent_editors()

    is_admin = (acl.action_allowed(request, 'Admin', '%')
                or acl.action_allowed(request, 'ReviewerAdminTools', 'View'))

    if is_admin and user_id:
        try:
            user = UserProfile.objects.get(pk=user_id)
        except UserProfile.DoesNotExist:
            pass  # Use request.user from above.

    motd_editable = acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit')

    monthly_data = _performance_by_month(user.id)
    performance_total = _performance_total(monthly_data)

    # Incentive point breakdown.
    today = date.today()
    month_ago = today - timedelta(days=30)
    year_ago = today - timedelta(days=365)
    point_total = ReviewerScore.get_total(user)
    totals = ReviewerScore.get_breakdown(user)
    months = ReviewerScore.get_breakdown_since(user, month_ago)
    years = ReviewerScore.get_breakdown_since(user, year_ago)

    def _sum(iter, types, exclude=False):
        """Sum the `total` property for items in `iter` that have an `atype`
        that is included in `types` when `exclude` is False (default) or not in
        `types` when `exclude` is True."""
        return sum(s.total for s in iter
                   if (s.atype in types) == (not exclude))

    breakdown = {
        'month': {
            'addons':
            _sum(months, amo.GROUP_TYPE_ADDON),
            'themes':
            _sum(months, amo.GROUP_TYPE_THEME),
            'other':
            _sum(months,
                 amo.GROUP_TYPE_ADDON + amo.GROUP_TYPE_THEME,
                 exclude=True)
        },
        'year': {
            'addons':
            _sum(years, amo.GROUP_TYPE_ADDON),
            'themes':
            _sum(years, amo.GROUP_TYPE_THEME),
            'other':
            _sum(years,
                 amo.GROUP_TYPE_ADDON + amo.GROUP_TYPE_THEME,
                 exclude=True)
        },
        'total': {
            'addons':
            _sum(totals, amo.GROUP_TYPE_ADDON),
            'themes':
            _sum(totals, amo.GROUP_TYPE_THEME),
            'other':
            _sum(totals,
                 amo.GROUP_TYPE_ADDON + amo.GROUP_TYPE_THEME,
                 exclude=True)
        }
    }

    data = context(request,
                   monthly_data=json.dumps(monthly_data),
                   performance_month=performance_total['month'],
                   performance_year=performance_total['year'],
                   breakdown=breakdown,
                   point_total=point_total,
                   editors=editors,
                   current_user=user,
                   is_admin=is_admin,
                   is_user=(request.user.id == user.id),
                   motd_editable=motd_editable)

    return render(request, 'editors/performance.html', data)
Exemplo n.º 59
0
 def has_permission(self, request, view):
     return ((request.method in SAFE_METHODS
              and acl.action_allowed(request, 'ReviewerTools', 'View'))
             or acl.check_addons_reviewer(request))
Exemplo n.º 60
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)