in src/olympia/reviewers/views.py [0:0]
def review(request, addon, channel=None):
whiteboard_url = reverse(
'reviewers.whiteboard',
args=(channel or 'listed', addon.pk),
)
channel, content_review = determine_channel(channel)
is_static_theme = addon.type == amo.ADDON_STATICTHEME
promoted_groups = addon.promoted_groups(currently_approved=False)
# Are we looking at an unlisted review page, or (weirdly) the listed
# review page of an unlisted-only add-on?
unlisted_only = channel == amo.CHANNEL_UNLISTED or not addon.has_listed_versions(
include_deleted=True
)
if unlisted_only and not acl.is_unlisted_addons_viewer_or_reviewer(request.user):
raise PermissionDenied
# Are we looking at a listed review page while only having content review
# permissions ? Redirect to content review page, it will be more useful.
if (
channel == amo.CHANNEL_LISTED
and content_review is False
and acl.action_allowed_for(request.user, amo.permissions.ADDONS_CONTENT_REVIEW)
and not acl.is_reviewer(request.user, addon, allow_content_reviewers=False)
):
return redirect('reviewers.review', 'content', addon.pk)
# Other cases are handled in ReviewHelper by limiting what actions are
# available depending on user permissions and add-on/version state.
is_admin = acl.action_allowed_for(request.user, amo.permissions.REVIEWS_ADMIN)
version = addon.find_latest_version(channel=channel, exclude=(), deleted=is_admin)
latest_not_disabled_version = addon.find_latest_version(channel=channel)
if not settings.ALLOW_SELF_REVIEWS and addon.has_author(request.user):
amo.messages.warning(request, 'Self-reviews are not allowed.')
return redirect(reverse('reviewers.dashboard'))
needs_human_review_qs = NeedsHumanReview.objects.filter(
is_active=True, version=OuterRef('pk')
)
# Queryset to be paginated for versions. We use the default ordering to get
# most recently created first (Note that the template displays each page
# in reverse order, older first).
versions_qs = (
# We want to load all Versions, even deleted ones, while using the
# addon.versions related manager to get `addon` property pre-cached on
# each version.
addon.versions(manager='unfiltered_for_relations')
.filter(channel=channel)
.select_related('autoapprovalsummary')
.select_related('reviewerflags')
.select_related('file___webext_permissions')
.select_related('blockversion')
# Prefetch needshumanreview existence into a property that the
# VersionsChoiceWidget will use.
.annotate(needs_human_review=Exists(needs_human_review_qs))
# Prefetch scanner results and related rules...
.prefetch_related('scannerresults')
.prefetch_related('scannerresults__matched_rules')
# Add activity transformer to prefetch all related activity logs on
# top of the regular transformers.
.transform(Version.transformer_activity)
# Add auto_approvable transformer to prefetch information about whether
# each version is auto-approvable or not.
.transform(Version.transformer_auto_approvable)
)
form_helper = ReviewHelper(
addon=addon,
version=version,
user=request.user,
content_review=content_review,
human_review=True,
)
form = ReviewForm(
request.POST if request.method == 'POST' else None,
request.FILES if request.method == 'POST' else None,
helper=form_helper,
)
reports = Paginator(AbuseReport.objects.for_addon(addon), 5).page(1)
user_ratings = Paginator(
(
Rating.without_replies.filter(
addon=addon, rating__lte=3, body__isnull=False
).order_by('-created')
),
5,
).page(1)
if channel == amo.CHANNEL_LISTED and is_static_theme:
redirect_url = reverse('reviewers.queue_theme')
else:
channel_arg = (
amo.CHANNEL_CHOICES_API.get(channel) if not content_review else 'content'
)
redirect_url = reverse('reviewers.review', args=[channel_arg, addon.pk])
if request.method == 'POST' and form.is_valid():
# Execute the action (is_valid() ensures the action is available to the
# reviewer)
form.helper.process()
amo.messages.success(request, 'Review successfully processed.')
clear_reviewing_cache(addon.id)
return redirect(form.helper.redirect_url or redirect_url)
# Kick off validation tasks for any files in this version which don't have
# cached validation, since reviewers will almost certainly need to access
# them. But only if we're not running in eager mode, since that could mean
# blocking page load for several minutes.
if version and not getattr(settings, 'CELERY_TASK_ALWAYS_EAGER', False):
if not version.file.has_been_validated:
devhub_tasks.validate(version.file)
actions = form.helper.actions.items()
# Find the previously approved version to compare to.
base_version_pk = version and (
addon.versions.exclude(id=version.id)
.filter(
# We're looking for a version that was either manually approved
# (either it has no auto approval summary, or it has one but
# with a negative verdict because it was locked by a reviewer
# who then approved it themselves), or auto-approved but then
# confirmed.
Q(autoapprovalsummary__isnull=True)
| Q(autoapprovalsummary__verdict=amo.NOT_AUTO_APPROVED)
| Q(
autoapprovalsummary__verdict=amo.AUTO_APPROVED,
autoapprovalsummary__confirmed=True,
)
)
.filter(
channel=channel,
file__isnull=False,
created__lt=version.created,
file__status=amo.STATUS_APPROVED,
)
.values_list('pk', flat=True)
.order_by('-created')
.first()
)
# The actions we shouldn't show a minimal form for.
actions_full = []
# The actions we should show the comments form for (contrary to minimal
# form above, it defaults to True, because most actions do need to have
# the comments form).
actions_comments = []
# The actions for which we should display the delayed rejection fields.
actions_delayable = []
# The actions for which we should display the reason select field.
actions_reasons = []
# The actions for which we should display the resolve abuse reports checkbox
actions_resolves_cinder_jobs = []
# The actions for which we should display the cinder policy select field.
actions_policies = []
# The actions for which to allow attachments.
actions_attachments = []
for key, action in actions:
if not (is_static_theme or action.get('minimal')):
actions_full.append(key)
if action.get('comments', True):
actions_comments.append(key)
if action.get('delayable', False):
actions_delayable.append(key)
if action.get('allows_reasons', False):
actions_reasons.append(key)
if action.get('requires_policies', False):
actions_policies.append(key)
if action.get('resolves_cinder_jobs', False):
actions_resolves_cinder_jobs.append(key)
if action.get('can_attach', True):
actions_attachments.append(key)
addons_sharing_same_guid = (
Addon.unfiltered.all()
.only_translations()
.filter(addonguid__guid=addon.addonguid_guid)
.exclude(pk=addon.pk)
.order_by('pk')
if addon.addonguid_guid
else []
)
approvals_info = None
if (
channel == amo.CHANNEL_LISTED
and addon.current_version
and addon.current_version.was_auto_approved
):
try:
approvals_info = addon.addonapprovalscounter
except AddonApprovalsCounter.DoesNotExist:
pass
pager = paginate(request, versions_qs, VERSIONS_PER_REVIEW_PAGE)
num_pages = pager.paginator.num_pages
count = pager.paginator.count
auto_approval_info = {}
version_ids = []
# Now that we've paginated the versions queryset, iterate on them to
# generate auto approvals info. Note that the variable should not clash
# the already existing 'version'.
for a_version in pager.object_list:
version_ids.append(a_version.pk)
if not a_version.is_ready_for_auto_approval:
continue
try:
summary = a_version.autoapprovalsummary
except AutoApprovalSummary.DoesNotExist:
auto_approval_info[a_version.pk] = None
continue
# Call calculate_verdict() again, it will use the data already stored.
verdict_info = summary.calculate_verdict(pretty=True)
auto_approval_info[a_version.pk] = verdict_info
versions_pending_rejection_qs = versions_qs.filter(
reviewerflags__pending_rejection__isnull=False
)
# We want to notify the reviewer if there are versions needing extra
# attention that are not present in the versions history (which is
# paginated).
versions_with_a_due_date_other = (
versions_qs.filter(due_date__isnull=False).exclude(pk__in=version_ids).count()
)
versions_flagged_by_mad_other = (
versions_qs.filter(reviewerflags__needs_human_review_by_mad=True)
.exclude(pk__in=version_ids)
.count()
)
versions_pending_rejection_other = versions_pending_rejection_qs.exclude(
pk__in=version_ids
).count()
# if the add-on was force-disabled we want to inform the reviewer what versions will
# be re-enabled automatically by a force enable action. (in all channels)
versions_that_would_be_enabled = (
()
if addon.status != amo.STATUS_DISABLED
else File.objects.disabled_that_would_be_renabled_with_addon()
.filter(version__addon=addon)
.order_by('-created')
.values_list(
'version__version', 'original_status', 'version__channel', named=True
)
)
flags = get_flags(addon, version) if version else []
try:
whiteboard = Whiteboard.objects.get(pk=addon.pk)
except Whiteboard.DoesNotExist:
whiteboard = Whiteboard(pk=addon.pk)
wb_form_cls = PublicWhiteboardForm if is_static_theme else WhiteboardForm
whiteboard_form = wb_form_cls(instance=whiteboard, prefix='whiteboard')
# Actions that are not tied to a specific version that we want to highlight
# in the "Add-on important changes history" section.
addon_important_changes_actions = (
# These are from developers themselves
amo.LOG.ADD_USER_WITH_ROLE.id,
amo.LOG.CHANGE_USER_WITH_ROLE.id,
amo.LOG.REMOVE_USER_WITH_ROLE.id,
# These are from admins or reviewers
amo.LOG.FORCE_DISABLE.id,
amo.LOG.HELD_ACTION_FORCE_DISABLE.id,
amo.LOG.FORCE_ENABLE.id,
amo.LOG.REQUEST_ADMIN_REVIEW_THEME.id,
amo.LOG.CLEAR_ADMIN_REVIEW_THEME.id,
amo.LOG.REQUEST_LEGAL.id,
)
important_changes_log = ActivityLog.objects.filter(
action__in=addon_important_changes_actions,
addonlog__addon=addon,
).order_by('id')
name_translations = (
addon.name.__class__.objects.filter(
id=addon.name.id, localized_string__isnull=False
).exclude(localized_string='')
if addon.name
else []
)
Addon._attach_authors([addon], listed=None, to_attr='current_authors')
ctx = context(
# Used for reviewer subscription check, don't use global `is_reviewer`
# since that actually is `is_user_any_kind_of_reviewer`.
acl_is_reviewer=acl.is_reviewer(request.user, addon),
acl_is_unlisted_addons_viewer_or_reviewer=(
acl.is_unlisted_addons_viewer_or_reviewer(request.user)
),
acl_is_review_moderator=(
acl.action_allowed_for(request.user, amo.permissions.RATINGS_MODERATE)
and request.user.is_staff
),
actions=actions,
actions_attachments=actions_attachments,
actions_comments=actions_comments,
actions_delayable=actions_delayable,
actions_full=actions_full,
actions_policies=actions_policies,
actions_reasons=actions_reasons,
actions_resolves_cinder_jobs=actions_resolves_cinder_jobs,
addon=addon,
addons_sharing_same_guid=addons_sharing_same_guid,
approvals_info=approvals_info,
auto_approval_info=auto_approval_info,
base_version_pk=base_version_pk,
channel=channel,
content_review=content_review,
count=count,
flags=flags,
form=form,
format_matched_rules=formatted_matched_rules_with_files_and_data,
has_versions_with_due_date_in_other_channel=addon.versions(
manager='unfiltered_for_relations'
)
.exclude(channel=channel)
.filter(due_date__isnull=False)
.exists(),
important_changes_log=important_changes_log,
is_admin=is_admin,
language_dict=dict(settings.LANGUAGES),
latest_not_disabled_version=latest_not_disabled_version,
latest_version_is_unreviewed_and_not_pending_rejection=(
version
and version.channel == amo.CHANNEL_LISTED
and version.is_unreviewed
and not version.pending_rejection
),
promoted_groups=promoted_groups,
name_translations=name_translations,
now=datetime.now(),
num_pages=num_pages,
pager=pager,
reports=reports,
session_id=request.session.session_key,
subscribed_listed=ReviewerSubscription.objects.filter(
user=request.user, addon=addon, channel=amo.CHANNEL_LISTED
).exists(),
subscribed_unlisted=ReviewerSubscription.objects.filter(
user=request.user, addon=addon, channel=amo.CHANNEL_UNLISTED
).exists(),
unlisted=(channel == amo.CHANNEL_UNLISTED),
user_ratings=user_ratings,
version=version,
VERSION_ADU_LIMIT=VERSION_ADU_LIMIT,
versions_that_would_be_enabled=versions_that_would_be_enabled,
MAX_VERSIONS_SHOWN_INLINE=MAX_VERSIONS_SHOWN_INLINE,
versions_with_a_due_date_other=versions_with_a_due_date_other,
versions_flagged_by_mad_other=versions_flagged_by_mad_other,
versions_pending_rejection_other=versions_pending_rejection_other,
whiteboard_form=whiteboard_form,
whiteboard_url=whiteboard_url,
)
return TemplateResponse(request, 'reviewers/review.html', context=ctx)