Exemple #1
0
def app_activity(request, addon_id):
    """Shows the app activity age for single app."""
    app = get_object_or_404(Webapp.with_deleted, pk=addon_id)

    user_items = ActivityLog.objects.for_apps([app]).exclude(
        action__in=mkt.LOG_HIDE_DEVELOPER)
    admin_items = ActivityLog.objects.for_apps([app]).filter(
        action__in=mkt.LOG_HIDE_DEVELOPER)

    user_items = paginate(request, user_items, per_page=20)
    admin_items = paginate(request, admin_items, per_page=20)

    return render(request, 'lookup/app_activity.html', {
        'admin_items': admin_items, 'app': app, 'user_items': user_items})
Exemple #2
0
def logs(request):
    data = request.GET.copy()

    if not data.get('start') and not data.get('end'):
        today = datetime.date.today()
        data['start'] = today - datetime.timedelta(days=30)

    form = forms.ReviewLogForm(data)

    approvals = ActivityLog.objects.review_queue(webapp=True)

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

    pager = paginate(request, approvals, 50)
    data = context(request,
                   form=form,
                   pager=pager,
                   ACTION_DICT=mkt.LOG_BY_ID,
                   tab='logs')
    return render(request, 'reviewers/logs.html', data)
Exemple #3
0
def logs(request):
    data = request.GET.copy()

    if not data.get('start') and not data.get('end'):
        today = datetime.date.today()
        data['start'] = today - datetime.timedelta(days=30)

    form = forms.ReviewLogForm(data)

    approvals = ActivityLog.objects.review_queue(webapp=True)

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

    pager = paginate(request, approvals, 50)
    data = context(request, form=form, pager=pager, ACTION_DICT=mkt.LOG_BY_ID,
                   tab='logs')
    return render(request, 'reviewers/logs.html', data)
Exemple #4
0
def app_abuse(request, addon):
    reports = AbuseReport.objects.filter(addon=addon).order_by('-created')
    total = reports.count()
    reports = paginate(request, reports, count=total)
    return render(request, 'reviewers/abuse.html',
                  context(request, item=addon, reports=reports,
                          total=total))
Exemple #5
0
def transactions(request):
    form, transactions = _get_transactions(request)
    return render(
        request, 'developers/transactions.html',
        {'form': form, 'CONTRIB_TYPES': mkt.CONTRIB_TYPES,
         'count': transactions.count(),
         'transactions': paginate(request, transactions, per_page=50)})
Exemple #6
0
def website_abuse(request, website):
    reports = AbuseReport.objects.filter(website=website).order_by('-created')
    total = reports.count()
    reports = paginate(request, reports, count=total)
    return render(request, 'reviewers/abuse.html',
                  context(request, item=website, reports=reports,
                          total=total))
Exemple #7
0
def app_abuse(request, addon):
    reports = AbuseReport.objects.filter(addon=addon).order_by('-created')
    total = reports.count()
    reports = paginate(request, reports, count=total)
    return render(request, 'reviewers/abuse.html',
                  context(request, item=addon, reports=reports,
                          total=total))
Exemple #8
0
def website_abuse(request, website):
    reports = AbuseReport.objects.filter(website=website).order_by('-created')
    total = reports.count()
    reports = paginate(request, reports, count=total)
    return render(request, 'reviewers/abuse.html',
                  context(request, item=website, reports=reports,
                          total=total))
Exemple #9
0
def user_summary(request, user_id):
    user = get_object_or_404(UserProfile, pk=user_id)
    is_admin = acl.action_allowed(request, 'Users', 'Edit')
    app_summary = _app_summary(user.pk)
    # All refunds that this user has requested (probably as a consumer).
    req = Refund.objects.filter(contribution__user=user)
    # All instantly-approved refunds that this user has requested.
    appr = req.filter(status=mkt.REFUND_APPROVED_INSTANT)
    refund_summary = {'approved': appr.count(),
                      'requested': req.count()}
    user_addons = user.addons.order_by('-created')
    user_addons = paginate(request, user_addons, per_page=15)

    payment_data = (AddonPaymentData.objects.filter(addon__authors=user)
                    .values(*AddonPaymentData.address_fields())
                    .distinct())

    # If the user is deleted, get the log detailing the delete.
    try:
        delete_log = ActivityLog.objects.for_user(user).filter(
            action=mkt.LOG.DELETE_USER_LOOKUP.id)[0]
    except IndexError:
        delete_log = None

    group_membership_formset = APIGroupMembershipFormSet()

    provider_portals = get_payment_provider_portals(user=user)
    return render(request, 'lookup/user_summary.html',
                  {'account': user, 'app_summary': app_summary,
                   'delete_form': DeleteUserForm(), 'delete_log': delete_log,
                   'is_admin': is_admin, 'refund_summary': refund_summary,
                   'user_addons': user_addons, 'payment_data': payment_data,
                   'provider_portals': provider_portals,
                   'group_membership_formset': group_membership_formset})
Exemple #10
0
def _queue(request,
           apps,
           tab,
           pager_processor=None,
           date_sort='created',
           template='reviewers/queue.html',
           data=None,
           use_es=False):
    per_page = request.GET.get('per_page', QUEUE_PER_PAGE)
    pager = paginate(request, apps, per_page)

    ctx = {
        'addons': pager.object_list,
        'pager': pager,
        'tab': tab,
        'search_form': _get_search_form(request),
        'date_sort': date_sort,
        'use_es': use_es,
    }

    # Additional context variables.
    if data is not None:
        ctx.update(data)

    return render(request, template, context(request, **ctx))
Exemple #11
0
def transactions(request):
    form, transactions = _get_transactions(request)
    return render(
        request, 'developers/transactions.html',
        {'form': form, 'CONTRIB_TYPES': mkt.CONTRIB_TYPES,
         'count': transactions.count(),
         'transactions': paginate(request, transactions, per_page=50)})
Exemple #12
0
def dashboard(request):
    addons, sorting = addon_listing(request)
    addons = paginate(request, addons, per_page=10)
    data = {
        'addons': addons,
        'sorting': sorting,
        'motd': unmemoized_get_config('mkt_developers_motd')
    }
    return render(request, 'developers/apps/dashboard.html', data)
Exemple #13
0
def dashboard(request):
    addons, sorting = addon_listing(request)
    addons = paginate(request, addons, per_page=10)
    data = {
        'addons': addons,
        'sorting': sorting,
        'motd': unmemoized_get_config('mkt_developers_motd')
    }
    return render(request, 'developers/apps/dashboard.html', data)
Exemple #14
0
def transactions(request):
    form, transactions = _get_transactions(request)
    return render(
        request,
        "developers/transactions.html",
        {
            "form": form,
            "CONTRIB_TYPES": mkt.CONTRIB_TYPES,
            "count": transactions.count(),
            "transactions": paginate(request, transactions, per_page=50),
        },
    )
Exemple #15
0
def moderatelog(request):
    form = ModerateLogForm(request.GET)
    modlog = ActivityLog.objects.editor_events()
    if form.is_valid():
        if form.cleaned_data['start']:
            modlog = modlog.filter(created__gte=form.cleaned_data['start'])
        if form.cleaned_data['end']:
            modlog = modlog.filter(created__lt=form.cleaned_data['end'])
        if form.cleaned_data['search']:
            modlog = modlog.filter(action=form.cleaned_data['search'].id)

    pager = paginate(request, modlog, 50)
    data = context(request, form=form, pager=pager, tab='moderatelog')
    return render(request, 'reviewers/moderatelog.html', data)
Exemple #16
0
def moderatelog(request):
    form = ModerateLogForm(request.GET)
    modlog = ActivityLog.objects.editor_events()
    if form.is_valid():
        if form.cleaned_data['start']:
            modlog = modlog.filter(created__gte=form.cleaned_data['start'])
        if form.cleaned_data['end']:
            modlog = modlog.filter(created__lt=form.cleaned_data['end'])
        if form.cleaned_data['search']:
            modlog = modlog.filter(action=form.cleaned_data['search'].id)

    pager = paginate(request, modlog, 50)
    data = context(request, form=form, pager=pager, tab='moderatelog')
    return render(request, 'reviewers/moderatelog.html', data)
Exemple #17
0
def queue_abuse_websites(request):
    """Queue for reviewing abuse reports for websites."""
    queues_helper = ReviewersQueuesHelper(request)
    sites = queues_helper.get_abuse_queue_websites()

    page = paginate(request, sites, per_page=20)
    abuse_formset = WebsiteAbuseViewFormSet(request.POST or None,
                                            queryset=page.object_list,
                                            request=request)

    if abuse_formset.is_valid():
        abuse_formset.save()
        return redirect(reverse('reviewers.websites.queue_abuse'))

    return render(request, 'reviewers/queue.html',
                  context(request, abuse_formset=abuse_formset,
                          tab='abusewebsites', page=page))
Exemple #18
0
def purchase_list(request, user):
    # Return all apps that the user has a contribution for as well as all apps
    # they have installed.
    def get_ids(qs):
        return list(qs.order_by('-id').values_list('addon_id', flat=True))

    contributed_apps_ids = get_ids(user.contribution_set.filter(type__in=[
        mkt.CONTRIB_PURCHASE, mkt.CONTRIB_REFUND, mkt.CONTRIB_CHARGEBACK]))

    installed_apps_ids = get_ids(user.installed_set.filter(install_type__in=[
        apps.INSTALL_TYPE_USER, apps.INSTALL_TYPE_DEVELOPER]).exclude(
        addon__in=contributed_apps_ids))

    addon_ids = contributed_apps_ids + installed_apps_ids
    qs = Webapp.objects.filter(id__in=addon_ids)
    products = paginate(request, manual_order(qs, addon_ids), count=qs.count())
    return products
Exemple #19
0
def queue_abuse(request):
    """Queue for reviewing abuse reports for apps."""
    queues_helper = ReviewersQueuesHelper(request)
    apps = queues_helper.get_abuse_queue()

    page = paginate(request, apps, per_page=20)
    abuse_formset = AppAbuseViewFormSet(request.POST or None,
                                        queryset=page.object_list,
                                        request=request)

    if abuse_formset.is_valid():
        abuse_formset.save()
        return redirect(reverse('reviewers.apps.queue_abuse'))

    return render(
        request, 'reviewers/queue.html',
        context(request, abuse_formset=abuse_formset, tab='abuse', page=page))
Exemple #20
0
def queue_moderated(request):
    """Queue for reviewing app reviews."""
    queues_helper = ReviewersQueuesHelper(request)
    qs = queues_helper.get_moderated_queue()

    page = paginate(request, qs, per_page=20)
    flags = dict(ReviewFlag.FLAGS)
    reviews_formset = ReviewFlagFormSet(request.POST or None,
                                        queryset=page.object_list,
                                        request=request)

    if reviews_formset.is_valid():
        reviews_formset.save()
        return redirect(reverse('reviewers.apps.queue_moderated'))

    return render(request, 'reviewers/queue.html',
                  context(request, reviews_formset=reviews_formset,
                          tab='moderated', page=page, flags=flags))
Exemple #21
0
def queue_moderated(request):
    """Queue for reviewing app reviews."""
    queues_helper = ReviewersQueuesHelper(request)
    qs = queues_helper.get_moderated_queue()

    page = paginate(request, qs, per_page=20)
    flags = dict(ReviewFlag.FLAGS)
    reviews_formset = ReviewFlagFormSet(request.POST or None,
                                        queryset=page.object_list,
                                        request=request)

    if reviews_formset.is_valid():
        reviews_formset.save()
        return redirect(reverse('reviewers.apps.queue_moderated'))

    return render(request, 'reviewers/queue.html',
                  context(request, reviews_formset=reviews_formset,
                          tab='moderated', page=page, flags=flags))
Exemple #22
0
def _queue(request, apps, tab, pager_processor=None, date_sort='created',
           template='reviewers/queue.html', data=None, use_es=False):
    per_page = request.GET.get('per_page', QUEUE_PER_PAGE)
    pager = paginate(request, apps, per_page)

    ctx = {
        'addons': pager.object_list,
        'pager': pager,
        'tab': tab,
        'search_form': _get_search_form(request),
        'date_sort': date_sort,
        'use_es': use_es,
    }

    # Additional context variables.
    if data is not None:
        ctx.update(data)

    return render(request, template, context(request, **ctx))
Exemple #23
0
def purchase_list(request, user):
    # Return all apps that the user has a contribution for as well as all apps
    # they have installed.
    def get_ids(qs):
        return list(qs.order_by('-id').values_list('addon_id', flat=True))

    contributed_apps_ids = get_ids(
        user.contribution_set.filter(type__in=[
            mkt.CONTRIB_PURCHASE, mkt.CONTRIB_REFUND, mkt.CONTRIB_CHARGEBACK
        ]))

    installed_apps_ids = get_ids(
        user.installed_set.filter(install_type__in=[
            apps.INSTALL_TYPE_USER, apps.INSTALL_TYPE_DEVELOPER
        ]).exclude(addon__in=contributed_apps_ids))

    addon_ids = contributed_apps_ids + installed_apps_ids
    qs = Webapp.objects.filter(id__in=addon_ids)
    products = paginate(request, manual_order(qs, addon_ids), count=qs.count())
    return products
Exemple #24
0
def dashboard(request):
    addons, sorting = addon_listing(request)
    addons = paginate(request, addons, per_page=10)
    data = {"addons": addons, "sorting": sorting, "motd": unmemoized_get_config("mkt_developers_motd")}
    return render(request, "developers/apps/dashboard.html", data)
Exemple #25
0
def preloads(request):
    preloads = (PreloadTestPlan.objects.filter(
        status=mkt.STATUS_PUBLIC).order_by('-created'))
    preloads = paginate(request, preloads, per_page=20)

    return render(request, 'operators/preloads.html', {'preloads': preloads})
Exemple #26
0
def preloads(request):
    preloads = (PreloadTestPlan.objects.filter(status=mkt.STATUS_PUBLIC)
                                       .order_by('-created'))
    preloads = paginate(request, preloads, per_page=20)

    return render(request, 'operators/preloads.html', {'preloads': preloads})
Exemple #27
0
def _review(request, addon, version):

    if (not settings.ALLOW_SELF_REVIEWS and
        not acl.action_allowed(request, 'Admin', '%') and
            addon.has_author(request.user)):
        messages.warning(request, _('Self-reviews are not allowed.'))
        return redirect(reverse('reviewers.home'))

    if (addon.status == mkt.STATUS_BLOCKED and
            not acl.action_allowed(request, 'Apps', 'ReviewEscalated')):
        messages.warning(
            request, _('Only senior reviewers can review blocklisted apps.'))
        return redirect(reverse('reviewers.home'))

    attachment_formset = CommAttachmentFormSet(data=request.POST or None,
                                               files=request.FILES or None,
                                               prefix='attachment')
    testedon_formset = TestedOnFormSet(data=request.POST or None,
                                       prefix='testedon')
    form = forms.get_review_form(data=request.POST or None,
                                 files=request.FILES or None, request=request,
                                 addon=addon, version=version,
                                 attachment_formset=attachment_formset,
                                 testedon_formset=testedon_formset)
    postdata = request.POST if request.method == 'POST' else None
    all_forms = [form, attachment_formset, testedon_formset]

    if version:
        features_list = version.features.to_names()
        appfeatures_form = AppFeaturesForm(data=postdata,
                                           instance=version.features)
        all_forms.append(appfeatures_form)
    else:
        appfeatures_form = None
        features_list = None

    queue_type = form.helper.review_type
    redirect_url = reverse('reviewers.apps.queue_%s' % queue_type)
    is_admin = acl.action_allowed(request, 'Apps', 'Edit')

    if request.method == 'POST' and all(f.is_valid() for f in all_forms):
        if form.cleaned_data.get('action') == 'public':
            old_types = set(o.id for o in addon.device_types)
            new_types = set(form.cleaned_data.get('device_override'))

            if old_types != new_types:
                # The reviewer overrode the device types. We need to not
                # publish this app immediately.
                if addon.publish_type == mkt.PUBLISH_IMMEDIATE:
                    addon.update(publish_type=mkt.PUBLISH_PRIVATE)

                # And update the device types to what the reviewer set.
                AddonDeviceType.objects.filter(addon=addon).delete()
                for device in form.cleaned_data.get('device_override'):
                    addon.addondevicetype_set.create(device_type=device)

                # Log that the reviewer changed the device types.
                added_devices = new_types - old_types
                removed_devices = old_types - new_types
                msg_list = [
                    _(u'Added {0}').format(unicode(mkt.DEVICE_TYPES[d].name))
                    for d in added_devices
                ] + [
                    _(u'Removed {0}').format(unicode(mkt.DEVICE_TYPES[d].name))
                    for d in removed_devices
                ]
                msg = _(u'Device(s) changed by '
                        u'reviewer: {0}').format(', '.join(msg_list))

                log_reviewer_action(addon, request.user, msg,
                                    mkt.LOG.REVIEW_DEVICE_OVERRIDE)

            if appfeatures_form.changed_data:
                # The reviewer overrode the requirements. We need to not
                # publish this app immediately.
                if addon.publish_type == mkt.PUBLISH_IMMEDIATE:
                    addon.update(publish_type=mkt.PUBLISH_PRIVATE)

                appfeatures_form.save(mark_for_rereview=False)

                # Log that the reviewer changed the minimum requirements.
                added_features, removed_features = (appfeatures_form
                                                    .get_changed_features())

                fmt = ', '.join(
                      [_(u'Added {0}').format(f) for f in added_features] +
                      [_(u'Removed {0}').format(f) for f in removed_features])
                # L10n: {0} is the list of requirements changes.
                msg = _(u'Requirements changed by reviewer: {0}').format(fmt)

                log_reviewer_action(addon, request.user, msg,
                                    mkt.LOG.REVIEW_FEATURES_OVERRIDE)

        score = form.helper.process()

        if form.cleaned_data.get('is_showcase'):
            if not addon.tags.filter(tag_text=SHOWCASE_TAG).exists():
                Tag(tag_text=SHOWCASE_TAG).save_tag(addon)
                recipient_list = (settings.APP_CURATION_BOARD_EMAIL,)
                subject = u'App [%s] nominated to be featured' % addon.name
                msg = (u'The Marketplace reviewer %s thinks %s (%s%s) is'
                       u'good enough to be a featured app.\n\n' % (
                           request.user, addon.name, settings.SITE_URL,
                           addon.get_url_path()))
                send_mail(subject, msg, recipient_list=recipient_list)
        else:
            Tag(tag_text=SHOWCASE_TAG).remove_tag(addon)

        # Success message.
        if score:
            score = ReviewerScore.objects.filter(user=request.user)[0]
            # L10N: {0} is the type of review. {1} is the points they earned.
            #       {2} is the points they now have total.
            success = _(
                u'"{0}" successfully processed (+{1} points, {2} total).'
                .format(unicode(mkt.REVIEWED_CHOICES[score.note_key]),
                        score.score,
                        ReviewerScore.get_total(request.user)))
        else:
            success = _('Review successfully processed.')
        messages.success(request, success)

        return redirect(redirect_url)

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

    try:
        if not version:
            raise Version.DoesNotExist
        show_diff = (addon.versions.exclude(id=version.id)
                                   .filter(files__isnull=False,
                                           created__lt=version.created,
                                           files__status=mkt.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')]

    # We only allow the user to check/uncheck files for "pending"
    allow_unchecking_files = form.helper.review_type == "pending"

    versions = (Version.with_deleted.filter(addon=addon)
                                    .order_by('-created')
                                    .transform(Version.transformer_activity)
                                    .transform(Version.transformer))

    product_attrs = {
        'product': json.dumps(
            product_as_dict(request, addon, False, 'reviewer'),
            cls=JSONEncoder),
        'manifest_url': addon.manifest_url,
    }

    pager = paginate(request, versions, 10)

    num_pages = pager.paginator.num_pages
    count = pager.paginator.count

    ctx = context(request, version=version, product=addon, pager=pager,
                  num_pages=num_pages, count=count,
                  form=form, canned=canned, is_admin=is_admin,
                  status_types=mkt.STATUS_CHOICES, show_diff=show_diff,
                  allow_unchecking_files=allow_unchecking_files,
                  actions=actions, actions_minimal=actions_minimal,
                  tab=queue_type, product_attrs=product_attrs,
                  attachment_formset=attachment_formset,
                  appfeatures_form=appfeatures_form,
                  testedon_formset=testedon_formset)

    if features_list is not None:
        ctx['feature_list'] = features_list

    return render(request, 'reviewers/review.html', ctx)
Exemple #28
0
def _review(request, addon, version):

    if (not settings.ALLOW_SELF_REVIEWS
            and not acl.action_allowed(request, 'Admin', '%')
            and addon.has_author(request.user)):
        messages.warning(request, _('Self-reviews are not allowed.'))
        return redirect(reverse('reviewers.home'))

    if (addon.status == mkt.STATUS_BLOCKED
            and not acl.action_allowed(request, 'Apps', 'ReviewEscalated')):
        messages.warning(
            request, _('Only senior reviewers can review blocklisted apps.'))
        return redirect(reverse('reviewers.home'))

    attachment_formset = CommAttachmentFormSet(data=request.POST or None,
                                               files=request.FILES or None,
                                               prefix='attachment')
    testedon_formset = TestedOnFormSet(data=request.POST or None,
                                       prefix='testedon')
    form = forms.get_review_form(data=request.POST or None,
                                 files=request.FILES or None,
                                 request=request,
                                 addon=addon,
                                 version=version,
                                 attachment_formset=attachment_formset,
                                 testedon_formset=testedon_formset)
    postdata = request.POST if request.method == 'POST' else None
    all_forms = [form, attachment_formset, testedon_formset]

    if version:
        features_list = version.features.to_names()
        appfeatures_form = AppFeaturesForm(data=postdata,
                                           instance=version.features)
        all_forms.append(appfeatures_form)
    else:
        appfeatures_form = None
        features_list = None

    queue_type = form.helper.review_type
    redirect_url = reverse('reviewers.apps.queue_%s' % queue_type)
    is_admin = acl.action_allowed(request, 'Apps', 'Edit')

    if request.method == 'POST' and all(f.is_valid() for f in all_forms):
        if form.cleaned_data.get('action') == 'public':
            old_types = set(o.id for o in addon.device_types)
            new_types = set(form.cleaned_data.get('device_override'))

            if old_types != new_types:
                # The reviewer overrode the device types. We need to not
                # publish this app immediately.
                if addon.publish_type == mkt.PUBLISH_IMMEDIATE:
                    addon.update(publish_type=mkt.PUBLISH_PRIVATE)

                # And update the device types to what the reviewer set.
                AddonDeviceType.objects.filter(addon=addon).delete()
                for device in form.cleaned_data.get('device_override'):
                    addon.addondevicetype_set.create(device_type=device)

                # Log that the reviewer changed the device types.
                added_devices = new_types - old_types
                removed_devices = old_types - new_types
                msg_list = [
                    _(u'Added {0}').format(unicode(mkt.DEVICE_TYPES[d].name))
                    for d in added_devices
                ] + [
                    _(u'Removed {0}').format(unicode(mkt.DEVICE_TYPES[d].name))
                    for d in removed_devices
                ]
                msg = _(u'Device(s) changed by '
                        u'reviewer: {0}').format(', '.join(msg_list))

                log_reviewer_action(addon, request.user, msg,
                                    mkt.LOG.REVIEW_DEVICE_OVERRIDE)

            if appfeatures_form.changed_data:
                # The reviewer overrode the requirements. We need to not
                # publish this app immediately.
                if addon.publish_type == mkt.PUBLISH_IMMEDIATE:
                    addon.update(publish_type=mkt.PUBLISH_PRIVATE)

                appfeatures_form.save(mark_for_rereview=False)

                # Log that the reviewer changed the minimum requirements.
                added_features, removed_features = (
                    appfeatures_form.get_changed_features())

                fmt = ', '.join(
                    [_(u'Added {0}').format(f) for f in added_features] +
                    [_(u'Removed {0}').format(f) for f in removed_features])
                # L10n: {0} is the list of requirements changes.
                msg = _(u'Requirements changed by reviewer: {0}').format(fmt)

                log_reviewer_action(addon, request.user, msg,
                                    mkt.LOG.REVIEW_FEATURES_OVERRIDE)

        score = form.helper.process()

        if form.cleaned_data.get('is_showcase'):
            if not addon.tags.filter(tag_text=SHOWCASE_TAG).exists():
                Tag(tag_text=SHOWCASE_TAG).save_tag(addon)
                recipient_list = (settings.APP_CURATION_BOARD_EMAIL, )
                subject = u'App [%s] nominated to be featured' % addon.name
                msg = (u'The Marketplace reviewer %s thinks %s (%s%s) is'
                       u'good enough to be a featured app.\n\n' %
                       (request.user, addon.name, settings.SITE_URL,
                        addon.get_url_path()))
                send_mail(subject, msg, recipient_list=recipient_list)
        else:
            Tag(tag_text=SHOWCASE_TAG).remove_tag(addon)

        # Success message.
        if score:
            score = ReviewerScore.objects.filter(user=request.user)[0]
            # L10N: {0} is the type of review. {1} is the points they earned.
            #       {2} is the points they now have total.
            success = _(
                u'"{0}" successfully processed (+{1} points, {2} total).'.
                format(unicode(mkt.REVIEWED_CHOICES[score.note_key]),
                       score.score, ReviewerScore.get_total(request.user)))
        else:
            success = _('Review successfully processed.')
        messages.success(request, success)

        return redirect(redirect_url)

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

    try:
        if not version:
            raise Version.DoesNotExist
        show_diff = (addon.versions.exclude(id=version.id).filter(
            files__isnull=False,
            created__lt=version.created,
            files__status=mkt.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')]

    # We only allow the user to check/uncheck files for "pending"
    allow_unchecking_files = form.helper.review_type == "pending"

    versions = (Version.with_deleted.filter(
        addon=addon).order_by('-created').transform(
            Version.transformer_activity).transform(Version.transformer))

    product_attrs = {
        'product':
        json.dumps(product_as_dict(request, addon, False, 'reviewer'),
                   cls=JSONEncoder),
        'manifest_url':
        addon.manifest_url,
    }

    pager = paginate(request, versions, 10)

    num_pages = pager.paginator.num_pages
    count = pager.paginator.count

    ctx = context(request,
                  version=version,
                  product=addon,
                  pager=pager,
                  num_pages=num_pages,
                  count=count,
                  form=form,
                  canned=canned,
                  is_admin=is_admin,
                  status_types=mkt.STATUS_CHOICES,
                  show_diff=show_diff,
                  allow_unchecking_files=allow_unchecking_files,
                  actions=actions,
                  actions_minimal=actions_minimal,
                  tab=queue_type,
                  product_attrs=product_attrs,
                  attachment_formset=attachment_formset,
                  appfeatures_form=appfeatures_form,
                  testedon_formset=testedon_formset)

    if features_list is not None:
        ctx['feature_list'] = features_list

    return render(request, 'reviewers/review.html', ctx)