def process(self, version): """Process a single version, figuring out if it should be auto-approved and calling the approval code if necessary.""" already_locked = AutoApprovalSummary.check_is_locked(version) if not already_locked: # Lock the addon for ourselves if possible. Even though # AutoApprovalSummary.create_summary_for_version() will do # call check_is_locked() again later when calculating the verdict, # we have to do it now to prevent overwriting an existing lock with # our own. set_reviewing_cache(version.addon.pk, settings.TASK_USER_ID) try: log.info('Processing %s version %s...', unicode(version.addon.name), unicode(version.version)) summary, info = AutoApprovalSummary.create_summary_for_version( version, dry_run=self.dry_run) log.info('Auto Approval for %s version %s: %s', unicode(version.addon.name), unicode(version.version), summary.get_verdict_display()) self.stats.update({k: int(v) for k, v in info.items()}) if summary.verdict == self.successful_verdict: self.stats['auto_approved'] += 1 if summary.verdict == amo.AUTO_APPROVED: self.approve(version) except (AutoApprovalNotEnoughFilesError, AutoApprovalNoValidationResultError): log.info( 'Version %s was skipped either because it had no ' 'file or because it had no validation attached.', version) self.stats['error'] += 1 finally: # Always clear our own lock no matter what happens (but only ours). if not already_locked: clear_reviewing_cache(version.addon.pk)
def process_addon(self, *, addon, now): latest_version = addon.find_latest_version( channel=amo.RELEASE_CHANNEL_LISTED) if (latest_version and latest_version.is_unreviewed and not latest_version.pending_rejection): # If latest version is unreviewed and not pending # rejection, we want to put the delayed rejection of all # versions of this addon on hold until a decision has been # made by reviewers on the latest one. log.info( 'Skipping rejections for add-on %s until version %s ' 'has been reviewed', addon.pk, latest_version.pk) return versions = self.fetch_version_candidates_for_addon(addon=addon, now=now) if not versions.exists(): log.info('Somehow no versions to auto-reject for add-on %s', addon.pk) return locked_by = get_reviewing_cache(addon.pk) if locked_by: # Don't auto-reject something that has been locked, even by the # task user - wait until it's free to avoid any conflicts. log.info( 'Skipping rejections for add-on %s until lock from user %s ' 'has expired', addon.pk, locked_by) return set_reviewing_cache(addon.pk, settings.TASK_USER_ID) try: self.reject_versions(addon=addon, versions=versions, latest_version=latest_version) finally: # Always clear our lock no matter what happens. clear_reviewing_cache(addon.pk)
def process(self, version): """Process a single version, figuring out if it should be auto-approved and calling the approval code if necessary.""" already_locked = AutoApprovalSummary.check_is_locked(version) if not already_locked: # Lock the addon for ourselves if possible. Even though # AutoApprovalSummary.create_summary_for_version() will do # call check_is_locked() again later when calculating the verdict, # we have to do it now to prevent overwriting an existing lock with # our own. set_reviewing_cache(version.addon.pk, settings.TASK_USER_ID) try: with transaction.atomic(): log.info('Processing %s version %s...', six.text_type(version.addon.name), six.text_type(version.version)) summary, info = AutoApprovalSummary.create_summary_for_version( version, dry_run=self.dry_run) self.stats.update({k: int(v) for k, v in info.items()}) if summary.verdict == self.successful_verdict: if summary.verdict == amo.AUTO_APPROVED: self.approve(version) self.stats['auto_approved'] += 1 verdict_string = summary.get_verdict_display() else: verdict_string = '%s (%s)' % ( summary.get_verdict_display(), ', '.join( summary.verdict_info_prettifier(info))) log.info('Auto Approval for %s version %s: %s', six.text_type(version.addon.name), six.text_type(version.version), verdict_string) # At this point, any exception should have rolled back the transaction, # so even if we did create/update an AutoApprovalSummary instance that # should have been rolled back. This ensures that, for instance, a # signing error doesn't leave the version and its autoapprovalsummary # in conflicting states. except (AutoApprovalNotEnoughFilesError, AutoApprovalNoValidationResultError): log.info( 'Version %s was skipped either because it had no ' 'files or because it had no validation attached.', version) self.stats['error'] += 1 except SigningError: log.info('Version %s was skipped because of a signing error', version) self.stats['error'] += 1 finally: # Always clear our own lock no matter what happens (but only ours). if not already_locked: clear_reviewing_cache(version.addon.pk)
def process(self, version): """Process a single version, figuring out if it should be auto-approved and calling the approval code if necessary.""" already_locked = AutoApprovalSummary.check_is_locked(version) if not already_locked: # Lock the addon for ourselves if possible. Even though # AutoApprovalSummary.create_summary_for_version() will do # call check_is_locked() again later when calculating the verdict, # we have to do it now to prevent overwriting an existing lock with # our own. set_reviewing_cache(version.addon.pk, settings.TASK_USER_ID) try: with transaction.atomic(): log.info('Processing %s version %s...', six.text_type(version.addon.name), six.text_type(version.version)) summary, info = AutoApprovalSummary.create_summary_for_version( version, dry_run=self.dry_run) log.info('Auto Approval for %s version %s: %s', six.text_type(version.addon.name), six.text_type(version.version), summary.get_verdict_display()) self.stats.update({k: int(v) for k, v in info.items()}) if summary.verdict == self.successful_verdict: if summary.verdict == amo.AUTO_APPROVED: self.approve(version) self.stats['auto_approved'] += 1 # At this point, any exception should have rolled back the transaction, # so even if we did create/update an AutoApprovalSummary instance that # should have been rolled back. This ensures that, for instance, a # signing error doesn't leave the version and its autoapprovalsummary # in conflicting states. except (AutoApprovalNotEnoughFilesError, AutoApprovalNoValidationResultError): log.info( 'Version %s was skipped either because it had no ' 'files or because it had no validation attached.', version) self.stats['error'] += 1 except SigningError: log.info( 'Version %s was skipped because of a signing error', version) self.stats['error'] += 1 finally: # Always clear our own lock no matter what happens (but only ours). if not already_locked: clear_reviewing_cache(version.addon.pk)
def 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)
def review(request, addon, channel=None): if channel == 'content': # 'content' is not a real channel, just a different review mode for # listed add-ons. content_review_only = True channel = 'listed' else: content_review_only = False # channel is passed in as text, but we want the constant. channel = amo.CHANNEL_CHOICES_LOOKUP.get(channel, amo.RELEASE_CHANNEL_LISTED) if content_review_only and not acl.action_allowed( request, amo.permissions.ADDONS_CONTENT_REVIEW): raise PermissionDenied unlisted_only = (channel == amo.RELEASE_CHANNEL_UNLISTED or not addon.has_listed_versions()) if unlisted_only and not acl.check_unlisted_addons_reviewer(request): raise PermissionDenied version = addon.find_latest_version(channel=channel, exclude=(amo.STATUS_BETA, )) if not settings.ALLOW_SELF_REVIEWS and addon.has_author(request.user): amo.messages.warning(request, ugettext('Self-reviews are not allowed.')) return redirect(reverse('reviewers.queue')) # Get the current info request state to set as the default. form_initial = {'info_request': version and version.has_info_request} form_helper = ReviewHelper(request=request, addon=addon, version=version, content_review_only=content_review_only) form = forms.ReviewForm(request.POST if request.method == 'POST' else None, helper=form_helper, initial=form_initial) is_admin = acl.action_allowed(request, amo.permissions.ADDONS_EDIT) is_post_reviewer = acl.action_allowed(request, amo.permissions.ADDONS_POST_REVIEW) approvals_info = None reports = None user_reviews = None was_auto_approved = False if channel == amo.RELEASE_CHANNEL_LISTED: if addon.current_version: was_auto_approved = addon.current_version.was_auto_approved if is_post_reviewer and version and version.is_webextension: try: approvals_info = addon.addonapprovalscounter except AddonApprovalsCounter.DoesNotExist: pass developers = addon.listed_authors reports = Paginator((AbuseReport.objects.filter( Q(addon=addon) | Q(user__in=developers)).order_by('-created')), 5).page(1) user_reviews = Paginator((Review.without_replies.filter( addon=addon, rating__lte=3, body__isnull=False).order_by('-created')), 5).page(1) if content_review_only: queue_type = 'content_review' elif was_auto_approved and is_post_reviewer: queue_type = 'auto_approved' else: queue_type = form.helper.handler.review_type redirect_url = reverse('reviewers.queue_%s' % queue_type) else: redirect_url = reverse('reviewers.unlisted_queue_all') if request.method == 'POST' and form.is_valid(): form.helper.process() if form.cleaned_data.get('notify'): ReviewerSubscription.objects.get_or_create(user=request.user, addon=addon) if form.cleaned_data.get('adminflag') and is_admin: addon.update(admin_review=False) amo.messages.success(request, ugettext('Review successfully processed.')) clear_reviewing_cache(addon.id) return redirect(redirect_url) # Kick off validation tasks for any files in this version which don't have # cached validation, since reviewers will almost certainly need to access # them. But only if we're not running in eager mode, since that could mean # blocking page load for several minutes. if version and not getattr(settings, 'CELERY_ALWAYS_EAGER', False): for file_ in version.all_files: if not file_.has_been_validated: devhub_tasks.validate(file_) canned = AddonCannedResponse.objects.all() actions = form.helper.actions.items() try: # Find the previously approved version to compare to. show_diff = version and (addon.versions.exclude(id=version.id).filter( # We're looking for a version that was either manually approved # or auto-approved but then confirmed. Q(autoapprovalsummary__isnull=True) | Q(autoapprovalsummary__verdict=amo.AUTO_APPROVED, autoapprovalsummary__confirmed=True)).filter( channel=channel, files__isnull=False, created__lt=version.created, files__status=amo.STATUS_PUBLIC).latest()) except Version.DoesNotExist: show_diff = None # The actions we should show a minimal form for. actions_minimal = [k for (k, a) in actions if not a.get('minimal')] # The actions we should show the comments form for (contrary to minimal # form above, it defaults to True, because most actions do need to have # the comments form). actions_comments = [k for (k, a) in actions if a.get('comments', True)] # The actions we should show the 'info request' checkbox for. actions_info_request = [ k for (k, a) in actions if a.get('info_request', False) ] versions = (Version.unfiltered.filter( addon=addon, channel=channel).select_related('autoapprovalsummary').exclude( files__status=amo.STATUS_BETA).order_by('-created').transform( Version.transformer_activity).transform(Version.transformer)) # We assume comments on old deleted versions are for listed versions. # See _get_comments_for_hard_deleted_versions above for more detail. all_versions = (_get_comments_for_hard_deleted_versions(addon) if channel == amo.RELEASE_CHANNEL_LISTED else []) all_versions.extend(versions) all_versions.sort(key=lambda v: v.created, reverse=True) pager = amo.utils.paginate(request, all_versions, 10) num_pages = pager.paginator.num_pages count = pager.paginator.count auto_approval_info = {} # Now that we've paginated the versions queryset, iterate on them to # generate auto approvals info. Note that the variable should not clash # the already existing 'version'. for a_version in pager.object_list: if not is_post_reviewer or not a_version.is_ready_for_auto_approval: continue try: summary = a_version.autoapprovalsummary except AutoApprovalSummary.DoesNotExist: auto_approval_info[a_version.pk] = None continue # Call calculate_verdict() again, it will use the data already stored. verdict_info = summary.calculate_verdict(pretty=True) auto_approval_info[a_version.pk] = verdict_info if version: flags = get_flags(version) else: flags = [] user_changes_actions = [ amo.LOG.ADD_USER_WITH_ROLE.id, amo.LOG.CHANGE_USER_WITH_ROLE.id, amo.LOG.REMOVE_USER_WITH_ROLE.id ] user_changes_log = AddonLog.objects.filter( activity_log__action__in=user_changes_actions, addon=addon).order_by('id') ctx = context(request, version=version, addon=addon, pager=pager, num_pages=num_pages, count=count, flags=flags, form=form, canned=canned, is_admin=is_admin, show_diff=show_diff, actions=actions, actions_minimal=actions_minimal, actions_comments=actions_comments, actions_info_request=actions_info_request, whiteboard_form=forms.WhiteboardForm(instance=addon), user_changes=user_changes_log, unlisted=(channel == amo.RELEASE_CHANNEL_UNLISTED), approvals_info=approvals_info, is_post_reviewer=is_post_reviewer, auto_approval_info=auto_approval_info, reports=reports, user_reviews=user_reviews, was_auto_approved=was_auto_approved, content_review_only=content_review_only) return render(request, 'reviewers/review.html', ctx)
def process(self, version): """Process a single version, figuring out if it should be auto-approved and calling the approval code if necessary.""" already_locked = AutoApprovalSummary.check_is_locked(version) if not already_locked: # Lock the addon for ourselves if possible. Even though # AutoApprovalSummary.create_summary_for_version() will do # call check_is_locked() again later when calculating the verdict, # we have to do it now to prevent overwriting an existing lock with # our own. set_reviewing_cache(version.addon.pk, settings.TASK_USER_ID) # Discard any existing celery tasks that may have been queued before: # If there are any left at this point, it means the transaction from # the previous loop iteration was not committed and we shouldn't # trigger the corresponding tasks. _discard_tasks() # Queue celery tasks for this version, avoiding triggering them too # soon... _start_queuing_tasks() try: with transaction.atomic(): # ...and release the queued tasks to celery once transaction # is committed. transaction.on_commit(_send_tasks_and_stop_queuing) log.info( 'Processing %s version %s...', str(version.addon.name), str(version.version), ) if waffle.switch_is_active('run-action-in-auto-approve'): # We want to execute `run_action()` only once. summary_exists = AutoApprovalSummary.objects.filter( version=version).exists() if summary_exists: log.info('Not running run_action() because it has ' 'already been executed') else: ScannerResult.run_action(version) summary, info = AutoApprovalSummary.create_summary_for_version( version, dry_run=self.dry_run) self.stats.update({k: int(v) for k, v in info.items()}) if summary.verdict == self.successful_verdict: if summary.verdict == amo.AUTO_APPROVED: self.approve(version) self.stats['auto_approved'] += 1 verdict_string = summary.get_verdict_display() else: verdict_string = '%s (%s)' % ( summary.get_verdict_display(), ', '.join(summary.verdict_info_prettifier(info)), ) log.info( 'Auto Approval for %s version %s: %s', str(version.addon.name), str(version.version), verdict_string, ) # At this point, any exception should have rolled back the transaction, # so even if we did create/update an AutoApprovalSummary instance that # should have been rolled back. This ensures that, for instance, a # signing error doesn't leave the version and its autoapprovalsummary # in conflicting states. except (AutoApprovalNotEnoughFilesError, AutoApprovalNoValidationResultError): log.info( 'Version %s was skipped either because it had no ' 'files or because it had no validation attached.', version, ) self.stats['error'] += 1 except SigningError: statsd.incr('reviewers.auto_approve.approve.failure') log.info('Version %s was skipped because of a signing error', version) self.stats['error'] += 1 finally: # Always clear our own lock no matter what happens (but only ours). if not already_locked: clear_reviewing_cache(version.addon.pk) # Stop post request task queue before moving on (useful in tests to # leave a fresh state for the next test. Note that we don't want to # send or clear queued tasks (they may belong to a transaction that # has been rolled back, or they may not have been processed by the # on commit handler yet). _stop_queuing_tasks()
def review(request, addon, channel=None): whiteboard_url = reverse( 'reviewers.whiteboard', args=(channel or 'listed', addon.slug if addon.slug else addon.pk)) channel, content_review_only = determine_channel(channel) was_auto_approved = ( channel == amo.RELEASE_CHANNEL_LISTED and addon.current_version and addon.current_version.was_auto_approved) is_static_theme = addon.type == amo.ADDON_STATICTHEME # If we're just looking (GET) we can bypass the specific permissions checks # if we have ReviewerTools:View. bypass_more_specific_permissions_because_read_only = ( request.method == 'GET' and acl.action_allowed( request, amo.permissions.REVIEWER_TOOLS_VIEW)) if not bypass_more_specific_permissions_because_read_only: perform_review_permission_checks( request, addon, channel, content_review_only=content_review_only) version = addon.find_latest_version(channel=channel, exclude=()) if not settings.ALLOW_SELF_REVIEWS and addon.has_author(request.user): amo.messages.warning( request, ugettext('Self-reviews are not allowed.')) return redirect(reverse('reviewers.queue')) # Get the current info request state to set as the default. form_initial = {'info_request': addon.pending_info_request} form_helper = ReviewHelper( request=request, addon=addon, version=version, content_review_only=content_review_only) form = ReviewForm(request.POST if request.method == 'POST' else None, helper=form_helper, initial=form_initial) is_admin = acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN) approvals_info = None reports = None user_ratings = None if channel == amo.RELEASE_CHANNEL_LISTED: if was_auto_approved: try: approvals_info = addon.addonapprovalscounter except AddonApprovalsCounter.DoesNotExist: pass developers = addon.listed_authors reports = Paginator( (AbuseReport.objects .filter(Q(addon=addon) | Q(user__in=developers)) .order_by('-created')), 5).page(1) user_ratings = Paginator( (Rating.without_replies .filter(addon=addon, rating__lte=3, body__isnull=False) .order_by('-created')), 5).page(1) if content_review_only: queue_type = 'content_review' elif was_auto_approved: queue_type = 'auto_approved' else: queue_type = form.helper.handler.review_type redirect_url = reverse('reviewers.queue_%s' % queue_type) else: redirect_url = reverse('reviewers.unlisted_queue_all') if request.method == 'POST' and form.is_valid(): form.helper.process() amo.messages.success( request, ugettext('Review successfully processed.')) clear_reviewing_cache(addon.id) return redirect(redirect_url) # Kick off validation tasks for any files in this version which don't have # cached validation, since reviewers will almost certainly need to access # them. But only if we're not running in eager mode, since that could mean # blocking page load for several minutes. if version and not getattr(settings, 'CELERY_ALWAYS_EAGER', False): for file_ in version.all_files: if not file_.has_been_validated: devhub_tasks.validate(file_) actions = form.helper.actions.items() try: # Find the previously approved version to compare to. show_diff = version and ( addon.versions.exclude(id=version.id).filter( # We're looking for a version that was either manually approved # (either it has no auto approval summary, or it has one but # with a negative verdict because it was locked by a reviewer # who then approved it themselves), or auto-approved but then # confirmed. Q(autoapprovalsummary__isnull=True) | Q(autoapprovalsummary__verdict=amo.NOT_AUTO_APPROVED) | Q(autoapprovalsummary__verdict=amo.AUTO_APPROVED, autoapprovalsummary__confirmed=True) ).filter( channel=channel, files__isnull=False, created__lt=version.created, files__status=amo.STATUS_PUBLIC).latest()) except Version.DoesNotExist: show_diff = None # The actions we shouldn't show a minimal form for. actions_full = [ k for (k, a) in actions if not (is_static_theme or a.get('minimal'))] # The actions we should show the comments form for (contrary to minimal # form above, it defaults to True, because most actions do need to have # the comments form). actions_comments = [k for (k, a) in actions if a.get('comments', True)] versions = (Version.unfiltered.filter(addon=addon, channel=channel) .select_related('autoapprovalsummary') .order_by('-created') .transform(Version.transformer_activity) .transform(Version.transformer)) # We assume comments on old deleted versions are for listed versions. # See _get_comments_for_hard_deleted_versions above for more detail. all_versions = (_get_comments_for_hard_deleted_versions(addon) if channel == amo.RELEASE_CHANNEL_LISTED else []) all_versions.extend(versions) all_versions.sort(key=lambda v: v.created, reverse=True) pager = amo.utils.paginate(request, all_versions, 10) num_pages = pager.paginator.num_pages count = pager.paginator.count auto_approval_info = {} # Now that we've paginated the versions queryset, iterate on them to # generate auto approvals info. Note that the variable should not clash # the already existing 'version'. for a_version in pager.object_list: if not a_version.is_ready_for_auto_approval: continue try: summary = a_version.autoapprovalsummary except AutoApprovalSummary.DoesNotExist: auto_approval_info[a_version.pk] = None continue # Call calculate_verdict() again, it will use the data already stored. verdict_info = summary.calculate_verdict(pretty=True) auto_approval_info[a_version.pk] = verdict_info flags = get_flags(addon, version) if version else [] if not is_static_theme: try: whiteboard = Whiteboard.objects.get(pk=addon.pk) except Whiteboard.DoesNotExist: whiteboard = Whiteboard(pk=addon.pk) whiteboard_form = WhiteboardForm( instance=whiteboard, prefix='whiteboard') else: whiteboard_form = None backgrounds = version.get_background_image_urls() if version else [] user_changes_actions = [ amo.LOG.ADD_USER_WITH_ROLE.id, amo.LOG.CHANGE_USER_WITH_ROLE.id, amo.LOG.REMOVE_USER_WITH_ROLE.id] user_changes_log = AddonLog.objects.filter( activity_log__action__in=user_changes_actions, addon=addon).order_by('id') ctx = context( request, actions=actions, actions_comments=actions_comments, actions_full=actions_full, addon=addon, api_token=request.COOKIES.get(API_TOKEN_COOKIE, None), approvals_info=approvals_info, auto_approval_info=auto_approval_info, backgrounds=backgrounds, content_review_only=content_review_only, count=count, flags=flags, form=form, is_admin=is_admin, num_pages=num_pages, pager=pager, reports=reports, show_diff=show_diff, subscribed=ReviewerSubscription.objects.filter( user=request.user, addon=addon).exists(), unlisted=(channel == amo.RELEASE_CHANNEL_UNLISTED), user_changes=user_changes_log, user_ratings=user_ratings, version=version, was_auto_approved=was_auto_approved, whiteboard_form=whiteboard_form, whiteboard_url=whiteboard_url) return render(request, 'reviewers/review.html', ctx)