Merge pre-review queues together (#20181)
* Merge pre-review queues together
This commit is contained in:
Родитель
45d3ee8099
Коммит
01e5260ff2
|
@ -10,7 +10,7 @@ from urllib.parse import urlsplit
|
|||
|
||||
from django.conf import settings
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import models, transaction
|
||||
from django.db.models import F, Max, Min, Q, signals as dbsignals
|
||||
from django.db.models.expressions import Func
|
||||
|
@ -50,7 +50,7 @@ from olympia.amo.utils import (
|
|||
to_language,
|
||||
)
|
||||
from olympia.constants.categories import CATEGORIES_BY_ID
|
||||
from olympia.constants.promoted import NOT_PROMOTED, PRE_REVIEW_GROUPS, RECOMMENDED
|
||||
from olympia.constants.promoted import NOT_PROMOTED, RECOMMENDED
|
||||
from olympia.constants.reviewers import REPUTATION_CHOICES
|
||||
from olympia.files.models import File
|
||||
from olympia.files.utils import extract_translations, resolve_i18n_message
|
||||
|
@ -240,7 +240,6 @@ class AddonManager(ManagerBase):
|
|||
admin_reviewer=False,
|
||||
content_review=False,
|
||||
theme_review=False,
|
||||
exclude_listed_pending_rejection=True,
|
||||
select_related_fields_for_listed=True,
|
||||
):
|
||||
qs = (
|
||||
|
@ -274,10 +273,6 @@ class AddonManager(ManagerBase):
|
|||
)
|
||||
qs = qs.select_related(*select_related_fields)
|
||||
|
||||
if exclude_listed_pending_rejection:
|
||||
qs = qs.filter(
|
||||
_current_version__reviewerflags__pending_rejection__isnull=True
|
||||
)
|
||||
if not admin_reviewer:
|
||||
if content_review:
|
||||
qs = qs.exclude(reviewerflags__needs_admin_content_review=True)
|
||||
|
@ -287,60 +282,86 @@ class AddonManager(ManagerBase):
|
|||
qs = qs.exclude(reviewerflags__needs_admin_code_review=True)
|
||||
return qs
|
||||
|
||||
def get_listed_pending_manual_approval_queue(
|
||||
self,
|
||||
admin_reviewer=False,
|
||||
recommendable=False,
|
||||
statuses=amo.VALID_ADDON_STATUSES,
|
||||
types=amo.GROUP_TYPE_ADDON,
|
||||
def get_queryset_for_pending_queues(
|
||||
self, *, admin_reviewer=False, theme_review=False, filters=None, excludes=None
|
||||
):
|
||||
if types not in (amo.GROUP_TYPE_ADDON, amo.GROUP_TYPE_THEME):
|
||||
raise ImproperlyConfigured(
|
||||
'types needs to be either GROUP_TYPE_ADDON or GROUP_TYPE_THEME'
|
||||
)
|
||||
theme_review = types == amo.GROUP_TYPE_THEME
|
||||
def pending_version_transformer(addons):
|
||||
version_ids = {addon.first_version_id for addon in addons}
|
||||
versions = {
|
||||
version.id: version
|
||||
for version in Version.objects.filter(
|
||||
id__in=version_ids
|
||||
).no_transforms()
|
||||
}
|
||||
for addon in addons:
|
||||
addon.first_pending_version = versions.get(addon.first_version_id)
|
||||
|
||||
if excludes is None:
|
||||
excludes = {}
|
||||
if filters is None:
|
||||
filters = {}
|
||||
qs = self.get_base_queryset_for_queue(
|
||||
admin_reviewer=admin_reviewer,
|
||||
# We'll filter on pending_rejection below without limiting
|
||||
# ourselves to the current_version.
|
||||
exclude_listed_pending_rejection=False,
|
||||
theme_review=theme_review,
|
||||
# Extension queue merges listed & unlisted together, so the select
|
||||
# related for listed don't make sense there, but the theme queues
|
||||
# don't do that, so they need them.
|
||||
select_related_fields_for_listed=theme_review,
|
||||
)
|
||||
filters = Q(
|
||||
status__in=statuses,
|
||||
type__in=types,
|
||||
versions__channel=amo.CHANNEL_LISTED,
|
||||
versions__file__status=amo.STATUS_AWAITING_REVIEW,
|
||||
versions__reviewerflags__pending_rejection=None,
|
||||
) & ~Q(disabled_by_user=True)
|
||||
if recommendable:
|
||||
filters &= Q(promotedaddon__group_id=RECOMMENDED.id)
|
||||
elif not theme_review:
|
||||
filters &= ~Q(promotedaddon__group_id=RECOMMENDED.id) & (
|
||||
Q(reviewerflags__auto_approval_disabled=True)
|
||||
| Q(reviewerflags__auto_approval_disabled_until_next_approval=True)
|
||||
| Q(reviewerflags__auto_approval_delayed_until__gt=datetime.now())
|
||||
| Q(
|
||||
promotedaddon__group_id__in=(
|
||||
group.id for group in PRE_REVIEW_GROUPS
|
||||
)
|
||||
)
|
||||
return (
|
||||
qs.filter(**filters)
|
||||
.exclude(**excludes)
|
||||
.annotate(
|
||||
first_version_due_date=Min('versions__due_date'),
|
||||
# Because of the Min(), a GROUP BY addon.id is created, and the
|
||||
# versions join will pick the first by due date. Unfortunately
|
||||
# if we were to annotate with just F('versions__<something>')
|
||||
# Django would add version.<something> to the GROUP BY, ruining
|
||||
# it. To prevent that, we wrap it into a harmless Func() - we
|
||||
# need a no-op function to do that, hence the ANY_VALUE().
|
||||
# We'll then grab the id in our transformer to fetch all
|
||||
# related versions.
|
||||
first_version_id=Func(F('versions__id'), function='ANY_VALUE'),
|
||||
)
|
||||
return qs.filter(filters).annotate(
|
||||
first_version_due=Min('versions__due_date'),
|
||||
# Because of the Min(), a GROUP BY addon.id is created.
|
||||
# Unfortunately if we were to annotate with just
|
||||
# F('versions__version') Django would add version.version to
|
||||
# the GROUP BY, ruining it. To prevent that, we wrap it into
|
||||
# a harmless Func() - we need a no-op function to do that,
|
||||
# hence the LOWER().
|
||||
latest_version=Func(F('versions__version'), function='LOWER'),
|
||||
.transform(pending_version_transformer)
|
||||
)
|
||||
|
||||
def get_pending_review_queue(self, admin_reviewer=False):
|
||||
filters = {
|
||||
'type__in': amo.GROUP_TYPE_ADDON,
|
||||
'versions__due_date__isnull': False,
|
||||
}
|
||||
excludes = {
|
||||
'status': amo.STATUS_DISABLED,
|
||||
}
|
||||
qs = self.get_queryset_for_pending_queues(
|
||||
admin_reviewer=admin_reviewer, filters=filters, excludes=excludes
|
||||
)
|
||||
return qs.select_related('promotedaddon')
|
||||
|
||||
def get_themes_pending_manual_approval_queue(
|
||||
self,
|
||||
admin_reviewer=False,
|
||||
statuses=amo.VALID_ADDON_STATUSES,
|
||||
):
|
||||
filters = {
|
||||
'type__in': amo.GROUP_TYPE_THEME,
|
||||
'versions__due_date__isnull': False,
|
||||
'status__in': statuses,
|
||||
}
|
||||
excludes = {
|
||||
'status': amo.STATUS_DISABLED,
|
||||
}
|
||||
return self.get_queryset_for_pending_queues(
|
||||
admin_reviewer=admin_reviewer,
|
||||
theme_review=True,
|
||||
filters=filters,
|
||||
excludes=excludes,
|
||||
)
|
||||
|
||||
def get_addons_with_unlisted_versions_queue(self, admin_reviewer=False):
|
||||
qs = self.get_base_queryset_for_queue(
|
||||
select_related_fields_for_listed=False,
|
||||
exclude_listed_pending_rejection=False,
|
||||
admin_reviewer=admin_reviewer,
|
||||
)
|
||||
# Reset *all* select_related() made by get_base_queryset_for_queue(),
|
||||
|
@ -350,30 +371,6 @@ class AddonManager(ManagerBase):
|
|||
status=amo.STATUS_DISABLED
|
||||
)
|
||||
|
||||
def get_unlisted_pending_manual_approval_queue(self, admin_reviewer=False):
|
||||
qs = self.get_base_queryset_for_queue(
|
||||
select_related_fields_for_listed=False,
|
||||
exclude_listed_pending_rejection=False,
|
||||
admin_reviewer=admin_reviewer,
|
||||
)
|
||||
filters = Q(
|
||||
versions__channel=amo.CHANNEL_UNLISTED,
|
||||
versions__file__status=amo.STATUS_AWAITING_REVIEW,
|
||||
type__in=amo.GROUP_TYPE_ADDON,
|
||||
versions__reviewerflags__pending_rejection__isnull=True,
|
||||
) & (
|
||||
Q(reviewerflags__auto_approval_disabled_unlisted=True)
|
||||
| Q(reviewerflags__auto_approval_disabled_until_next_approval_unlisted=True)
|
||||
| Q(reviewerflags__auto_approval_delayed_until__gt=datetime.now())
|
||||
)
|
||||
return qs.filter(filters).annotate(
|
||||
# These annotations should be applied to versions that match the
|
||||
# filters above. They'll be used to sort the results or just
|
||||
# display the data to reviewers in the queue.
|
||||
first_version_created=Min('versions__created'),
|
||||
worst_score=Max('versions__autoapprovalsummary__score'),
|
||||
)
|
||||
|
||||
def get_auto_approved_queue(self, admin_reviewer=False):
|
||||
"""Return a queryset of Addon objects that have been auto-approved but
|
||||
not confirmed by a human yet."""
|
||||
|
@ -381,7 +378,10 @@ class AddonManager(ManagerBase):
|
|||
qs = (
|
||||
self.get_base_queryset_for_queue(admin_reviewer=admin_reviewer)
|
||||
.public()
|
||||
.filter(_current_version__autoapprovalsummary__verdict=success_verdict)
|
||||
.filter(
|
||||
_current_version__autoapprovalsummary__verdict=success_verdict,
|
||||
_current_version__reviewerflags__pending_rejection__isnull=True,
|
||||
)
|
||||
.exclude(_current_version__autoapprovalsummary__confirmed=True)
|
||||
.order_by(
|
||||
'-_current_version__autoapprovalsummary__score',
|
||||
|
@ -400,6 +400,7 @@ class AddonManager(ManagerBase):
|
|||
)
|
||||
.valid()
|
||||
.filter(
|
||||
_current_version__reviewerflags__pending_rejection__isnull=True,
|
||||
addonapprovalscounter__last_content_review=None,
|
||||
# Only content review extensions and dictionaries. See
|
||||
# https://github.com/mozilla/addons-server/issues/11796 &
|
||||
|
@ -417,12 +418,7 @@ class AddonManager(ManagerBase):
|
|||
disabled versions - typically scanners queues, where anything could be
|
||||
flagged, approved or waiting for review."""
|
||||
return (
|
||||
self.get_base_queryset_for_queue(
|
||||
admin_reviewer=admin_reviewer,
|
||||
# We'll filter on pending_rejection below without limiting
|
||||
# ourselves to the current_version.
|
||||
exclude_listed_pending_rejection=False,
|
||||
)
|
||||
self.get_base_queryset_for_queue(admin_reviewer=admin_reviewer)
|
||||
.filter(
|
||||
# Only extensions to avoid looking at themes etc, which slows
|
||||
# the query down.
|
||||
|
@ -482,10 +478,7 @@ class AddonManager(ManagerBase):
|
|||
def get_pending_rejection_queue(self, admin_reviewer=False):
|
||||
filter_kwargs = {'versions__reviewerflags__pending_rejection__isnull': False}
|
||||
return (
|
||||
self.get_base_queryset_for_queue(
|
||||
admin_reviewer=admin_reviewer,
|
||||
exclude_listed_pending_rejection=False,
|
||||
)
|
||||
self.get_base_queryset_for_queue(admin_reviewer=admin_reviewer)
|
||||
.filter(**filter_kwargs)
|
||||
.order_by('_current_version__reviewerflags__pending_rejection')
|
||||
.distinct()
|
||||
|
@ -1777,6 +1770,9 @@ class Addon(OnChangeMixin, ModelBase):
|
|||
self.tag_list = new_tag_list
|
||||
|
||||
def update_all_due_dates(self):
|
||||
"""
|
||||
Update all due dates on versions of this add-on.
|
||||
"""
|
||||
for version in self.versions.should_have_due_date().filter(
|
||||
due_date__isnull=True
|
||||
):
|
||||
|
|
|
@ -50,7 +50,6 @@ from olympia.files.utils import ManifestJSONExtractor, parse_addon
|
|||
from olympia.git.models import GitExtractionEntry
|
||||
from olympia.promoted.models import PromotedAddon
|
||||
from olympia.ratings.models import Rating, RatingFlag
|
||||
from olympia.reviewers.models import AutoApprovalSummary
|
||||
from olympia.translations.models import (
|
||||
Translation,
|
||||
TranslationSequence,
|
||||
|
@ -3042,229 +3041,143 @@ class TestExtensionsQueues(TestCase):
|
|||
assert all(addon in addons for addon in expected_addons.values())
|
||||
assert not any(addon in addons for addon in unexpected_addons.values())
|
||||
|
||||
|
||||
class TestUnlistedPendingManualApprovalQueue(TestCase):
|
||||
def test_basic(self):
|
||||
# Shouldn't be in the queue:
|
||||
addon_factory()
|
||||
addon_factory(version_kw={'channel': amo.CHANNEL_UNLISTED})
|
||||
addon_factory(
|
||||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon_factory(
|
||||
type=amo.ADDON_STATICTHEME,
|
||||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
),
|
||||
auto_approval_disabled_unlisted=True,
|
||||
)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon_factory(
|
||||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
),
|
||||
auto_approval_disabled=True,
|
||||
)
|
||||
deleted_addon = AddonReviewerFlags.objects.create(
|
||||
addon=addon_factory(
|
||||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
),
|
||||
auto_approval_disabled_until_next_approval_unlisted=True,
|
||||
).addon
|
||||
deleted_addon.delete()
|
||||
|
||||
# Should be in the queue:
|
||||
expected = [
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=version_factory(
|
||||
addon=addon_factory(),
|
||||
channel=amo.CHANNEL_UNLISTED,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
).addon,
|
||||
auto_approval_disabled_unlisted=True,
|
||||
).addon,
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon_factory(
|
||||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
),
|
||||
auto_approval_disabled_until_next_approval_unlisted=True,
|
||||
).addon,
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon_factory(
|
||||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
),
|
||||
auto_approval_delayed_until=datetime.now() + timedelta(days=99),
|
||||
).addon,
|
||||
]
|
||||
|
||||
results = Addon.objects.get_unlisted_pending_manual_approval_queue()
|
||||
assert len(results) == len(expected)
|
||||
assert set(results) == set(expected)
|
||||
|
||||
def test_worst_score_and_first_created_ignores_versions_not_in_filter(self):
|
||||
expected_first_created_date = self.days_ago(2)
|
||||
addon = AddonReviewerFlags.objects.create(
|
||||
addon=addon_factory(
|
||||
version_kw={
|
||||
'channel': amo.CHANNEL_UNLISTED,
|
||||
'created': self.days_ago(1),
|
||||
},
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
),
|
||||
auto_approval_disabled_unlisted=True,
|
||||
).addon
|
||||
AutoApprovalSummary.objects.create(version=addon.versions.all()[0], score=66)
|
||||
AutoApprovalSummary.objects.create(
|
||||
version=version_factory(
|
||||
addon=addon,
|
||||
channel=amo.CHANNEL_UNLISTED,
|
||||
created=expected_first_created_date,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
),
|
||||
score=55,
|
||||
)
|
||||
# None of the following should matter:
|
||||
AutoApprovalSummary.objects.create(
|
||||
version=version_factory(
|
||||
addon=addon,
|
||||
created=self.days_ago(99),
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
),
|
||||
score=99,
|
||||
)
|
||||
AutoApprovalSummary.objects.create(
|
||||
version=version_factory(
|
||||
addon=addon,
|
||||
channel=amo.CHANNEL_UNLISTED,
|
||||
created=self.days_ago(98),
|
||||
file_kw={'status': amo.STATUS_APPROVED},
|
||||
),
|
||||
score=98,
|
||||
)
|
||||
AutoApprovalSummary.objects.create(
|
||||
version=version_factory(
|
||||
addon=addon,
|
||||
created=self.days_ago(97),
|
||||
file_kw={'status': amo.STATUS_DISABLED},
|
||||
),
|
||||
score=97,
|
||||
)
|
||||
|
||||
results = Addon.objects.get_unlisted_pending_manual_approval_queue()
|
||||
assert len(results) == 1
|
||||
result = results[0]
|
||||
assert result == addon
|
||||
assert result.worst_score == 66
|
||||
assert result.first_version_created == expected_first_created_date
|
||||
|
||||
|
||||
class TestListedPendingManualApprovalQueue(TestCase):
|
||||
def test_basic(self):
|
||||
# Shouldn't be in the queue:
|
||||
addon_factory()
|
||||
addon_factory(
|
||||
type=amo.ADDON_STATICTHEME,
|
||||
status=amo.STATUS_NOMINATED,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
addon_factory(
|
||||
status=amo.STATUS_NULL,
|
||||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
version_factory(
|
||||
addon=addon_factory(),
|
||||
channel=amo.CHANNEL_UNLISTED,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
addon_factory(
|
||||
status=amo.STATUS_NOMINATED, file_kw={'status': amo.STATUS_AWAITING_REVIEW}
|
||||
),
|
||||
version_factory(
|
||||
addon=addon_factory(), file_kw={'status': amo.STATUS_AWAITING_REVIEW}
|
||||
)
|
||||
version_review_flags_factory(
|
||||
version=version_factory(
|
||||
addon=addon_factory(), file_kw={'status': amo.STATUS_AWAITING_REVIEW}
|
||||
),
|
||||
pending_rejection=datetime.now() + timedelta(days=1),
|
||||
)
|
||||
PromotedAddon.objects.create(
|
||||
addon=version_factory(
|
||||
addon=addon_factory(), file_kw={'status': amo.STATUS_AWAITING_REVIEW}
|
||||
).addon,
|
||||
group_id=RECOMMENDED.id,
|
||||
).addon
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon_factory(
|
||||
def test_pending_queue(self):
|
||||
expected_addons = [
|
||||
addon_factory(
|
||||
name='Listed with auto-approval disabled',
|
||||
status=amo.STATUS_NOMINATED,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
reviewer_flags={
|
||||
'auto_approval_disabled': True,
|
||||
'needs_admin_content_review': True,
|
||||
},
|
||||
),
|
||||
auto_approval_delayed_until=datetime.now() - timedelta(days=1),
|
||||
)
|
||||
|
||||
# Should be in the queue:
|
||||
expected = [
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon_factory(
|
||||
name='Pure unlisted with auto-approval disabled',
|
||||
status=amo.STATUS_NULL,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
reviewer_flags={
|
||||
'auto_approval_disabled_unlisted': True,
|
||||
},
|
||||
),
|
||||
version_factory(
|
||||
addon=addon_factory(
|
||||
status=amo.STATUS_NOMINATED,
|
||||
file_kw={
|
||||
'status': amo.STATUS_AWAITING_REVIEW,
|
||||
name='Mixed with auto-approval disabled for unlisted',
|
||||
reviewer_flags={
|
||||
'auto_approval_disabled_unlisted': True,
|
||||
},
|
||||
),
|
||||
auto_approval_disabled=True,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
channel=amo.CHANNEL_UNLISTED,
|
||||
).addon,
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon_factory(
|
||||
status=amo.STATUS_NOMINATED,
|
||||
file_kw={
|
||||
'status': amo.STATUS_AWAITING_REVIEW,
|
||||
},
|
||||
),
|
||||
auto_approval_disabled_until_next_approval=True,
|
||||
version_factory(
|
||||
addon=version_factory(
|
||||
addon=addon_factory(
|
||||
name='Mixed with both channel awaiting review',
|
||||
reviewer_flags={
|
||||
'auto_approval_disabled_unlisted': True,
|
||||
'auto_approval_disabled': True,
|
||||
},
|
||||
),
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
channel=amo.CHANNEL_UNLISTED,
|
||||
).addon,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
).addon,
|
||||
AddonReviewerFlags.objects.create(
|
||||
version_factory(
|
||||
addon=addon_factory(
|
||||
status=amo.STATUS_NOMINATED,
|
||||
file_kw={
|
||||
'status': amo.STATUS_AWAITING_REVIEW,
|
||||
},
|
||||
name='Listed already public with auto-approval disabled',
|
||||
reviewer_flags={'auto_approval_disabled': True},
|
||||
),
|
||||
auto_approval_delayed_until=datetime.now() + timedelta(days=1),
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
).addon,
|
||||
]
|
||||
|
||||
qs = Addon.objects.get_listed_pending_manual_approval_queue().order_by('pk')
|
||||
assert list(qs) == expected
|
||||
|
||||
def test_versions_annotations(self):
|
||||
due_1 = self.days_ago(-2)
|
||||
# Add add-ons that should not appear. For some it's because of
|
||||
# something we're explicitly filtering out, for others it's because of
|
||||
# something that causes the factories not to generate a due date for
|
||||
# them.
|
||||
addon_factory(name='Fully Public Add-on')
|
||||
addon_factory(
|
||||
status=amo.STATUS_NOMINATED,
|
||||
name='Pure unlisted',
|
||||
status=amo.STATUS_NULL,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
)
|
||||
addon_factory(
|
||||
name='Add-on that will be auto-approved',
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
version_factory(
|
||||
addon=addon_factory(name='Add-on with version that will be auto-approved'),
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
addon_factory(
|
||||
name='Theme',
|
||||
type=amo.ADDON_STATICTHEME,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
reviewer_flags={'auto_approval_disabled': True},
|
||||
version_kw={'due_date': due_1},
|
||||
)
|
||||
due_2 = self.days_ago(1)
|
||||
version_factory(
|
||||
addon=addon_factory(
|
||||
reviewer_flags={'auto_approval_disabled': True},
|
||||
version_kw={'due_date': self.days_ago(4)},
|
||||
),
|
||||
addon_factory(
|
||||
name='Disabled by Mozilla',
|
||||
status=amo.STATUS_DISABLED,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
due_date=due_2,
|
||||
reviewer_flags={'auto_approval_disabled': True},
|
||||
)
|
||||
expected = [due_1, due_2]
|
||||
qs = Addon.objects.get_listed_pending_manual_approval_queue().order_by('pk')
|
||||
result = [addon.first_version_due for addon in qs]
|
||||
assert result == expected
|
||||
version_review_flags_factory(
|
||||
version=addon_factory(
|
||||
name='Pending rejection',
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
).current_version,
|
||||
pending_rejection=datetime.now(),
|
||||
)
|
||||
addon_factory(
|
||||
name='Deleted add-on',
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
reviewer_flags={'auto_approval_disabled': True},
|
||||
).delete()
|
||||
addon_factory(
|
||||
name='Deleted unlisted version',
|
||||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
reviewer_flags={'auto_approval_disabled_unlisted': True},
|
||||
).versions.all()[0].delete()
|
||||
addon_factory(
|
||||
name='Deleted listed version',
|
||||
version_kw={'channel': amo.CHANNEL_LISTED},
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
reviewer_flags={'auto_approval_disabled': True},
|
||||
).versions.all()[0].delete()
|
||||
addon_needing_admin_code_review = addon_factory(
|
||||
name='Listed with auto-approval disabled needing code review',
|
||||
status=amo.STATUS_NOMINATED,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
reviewer_flags={
|
||||
'auto_approval_disabled': True,
|
||||
'needs_admin_code_review': True,
|
||||
},
|
||||
)
|
||||
addons = Addon.objects.get_pending_review_queue()
|
||||
assert list(addons.order_by('pk')) == expected_addons
|
||||
|
||||
def test_staticthemes(self):
|
||||
# Test that we picked the version with the oldest due date and that we
|
||||
# added the first_pending_version property.
|
||||
for addon in addons:
|
||||
expected_version = (
|
||||
addon.versions.exclude(due_date=None).order_by('due_date').first()
|
||||
)
|
||||
assert expected_version
|
||||
assert addon.first_pending_version == expected_version
|
||||
|
||||
# Admins should be able to see addons needing admin code review.
|
||||
expected_addons.append(addon_needing_admin_code_review)
|
||||
addons = Addon.objects.get_pending_review_queue(admin_reviewer=True)
|
||||
assert set(addons) == set(expected_addons)
|
||||
|
||||
|
||||
class TestThemesPendingManualApprovalQueue(TestCase):
|
||||
def test_basic(self):
|
||||
expected = [
|
||||
addon_factory(
|
||||
type=amo.ADDON_STATICTHEME,
|
||||
|
@ -3276,30 +3189,31 @@ class TestListedPendingManualApprovalQueue(TestCase):
|
|||
status=amo.STATUS_NOMINATED,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
qs = Addon.objects.get_listed_pending_manual_approval_queue(
|
||||
types=amo.GROUP_TYPE_THEME
|
||||
).order_by('pk')
|
||||
qs = Addon.objects.get_themes_pending_manual_approval_queue().order_by('pk')
|
||||
assert list(qs) == expected
|
||||
|
||||
def test_with_recommended(self):
|
||||
def test_approved_theme_pending_version(self):
|
||||
expected = [
|
||||
PromotedAddon.objects.create(
|
||||
addon=version_factory(
|
||||
addon=addon_factory(),
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
).addon,
|
||||
group_id=RECOMMENDED.id,
|
||||
).addon
|
||||
addon_factory(
|
||||
type=amo.ADDON_STATICTHEME,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
]
|
||||
qs = Addon.objects.get_listed_pending_manual_approval_queue(
|
||||
recommendable=True
|
||||
addon_factory(
|
||||
type=amo.ADDON_STATICTHEME,
|
||||
status=amo.STATUS_NOMINATED,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
addon_factory(type=amo.ADDON_STATICTHEME)
|
||||
addon_factory(
|
||||
status=amo.STATUS_NOMINATED,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
qs = Addon.objects.get_themes_pending_manual_approval_queue(
|
||||
statuses=(amo.STATUS_APPROVED,)
|
||||
).order_by('pk')
|
||||
assert list(qs) == expected
|
||||
|
||||
def test_query_only_group_by_on_addon_id(self):
|
||||
sql = str(Addon.objects.get_listed_pending_manual_approval_queue().query)
|
||||
assert sql.endswith('GROUP BY `addons`.`id` ORDER BY NULL')
|
||||
|
||||
|
||||
class TestAddonGUID(TestCase):
|
||||
def test_creates_hashed_guid_on_save(self):
|
||||
|
|
|
@ -1,38 +1,36 @@
|
|||
{% if num_pages > 1 %}
|
||||
<ol class="pagination">
|
||||
{% if pager.has_previous() %}
|
||||
<li>
|
||||
<a rel="prev" href="{{ pager.url|urlparams(page=pager.previous_page_number()) }}">
|
||||
{{ _('Prev') }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if pager.dotted_lower %}
|
||||
<li><a href="{{ pager.url|urlparams(page=1) }}">{{ 1 }}</a></li>
|
||||
<li class="skip">…</li>
|
||||
{% endif %}
|
||||
{% for x in pager.page_range %}
|
||||
<li {{ x|class_selected(pager.number) }}>
|
||||
<a href="{{ pager.url|urlparams(page=x) }}">{{ x }}</a>
|
||||
<ol class="pagination">
|
||||
{% if pager.has_previous() %}
|
||||
<li>
|
||||
<a rel="prev" href="{{ pager.url|urlparams(page=pager.previous_page_number()) }}">
|
||||
{{ _('Prev') }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if pager.dotted_upper %}
|
||||
<li class="skip">…</li>
|
||||
<li><a href="{{ pager.url|urlparams(page=num_pages) }}">{{ num_pages }}</a></li>
|
||||
{% endif %}
|
||||
{% if pager.has_next() %}
|
||||
<li>
|
||||
<a rel="next" href="{{ pager.url|urlparams(page=pager.next_page_number()) }}">
|
||||
{{ _('Next') }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ol>
|
||||
<div class="num-results">
|
||||
{% trans begin=pager.start_index(), end=pager.end_index(),
|
||||
count=count|numberfmt %}
|
||||
Results <strong>{{ begin }}</strong>–<strong>{{ end }}</strong>
|
||||
of <strong>{{ count }}</strong>
|
||||
{% endtrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if pager.dotted_lower %}
|
||||
<li><a href="{{ pager.url|urlparams(page=1) }}">{{ 1 }}</a></li>
|
||||
<li class="skip">…</li>
|
||||
{% endif %}
|
||||
{% for x in pager.page_range %}
|
||||
<li {{ x|class_selected(pager.number) }}>
|
||||
<a href="{{ pager.url|urlparams(page=x) }}">{{ x }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if pager.dotted_upper %}
|
||||
<li class="skip">…</li>
|
||||
<li><a href="{{ pager.url|urlparams(page=num_pages) }}">{{ num_pages }}</a></li>
|
||||
{% endif %}
|
||||
{% if pager.has_next() %}
|
||||
<li>
|
||||
<a rel="next" href="{{ pager.url|urlparams(page=pager.next_page_number()) }}">
|
||||
{{ _('Next') }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ol>
|
||||
<div class="num-results">
|
||||
{% trans begin=pager.start_index(), end=pager.end_index(),
|
||||
count=count|numberfmt %}
|
||||
Results <strong>{{ begin }}</strong>–<strong>{{ end }}</strong>
|
||||
of <strong>{{ count }}</strong>
|
||||
{% endtrans %}
|
||||
</div>
|
||||
|
|
|
@ -62,7 +62,9 @@
|
|||
<div class="items">
|
||||
{{ dev_addon_listing_items(addons.object_list) }}
|
||||
</div>
|
||||
{{ addons|paginator }}
|
||||
{% if addons.paginator.num_pages > 1 %}
|
||||
{{ addons|paginator }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
@ -80,7 +82,9 @@
|
|||
<div class="items">
|
||||
{{ dev_addon_listing_items(themes.object_list) }}
|
||||
</div>
|
||||
{{ themes|paginator }}
|
||||
{% if themes.paginator.num_pages > 1 %}
|
||||
{{ themes|paginator }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
|
|
@ -28,11 +28,9 @@
|
|||
|
||||
<div class="queue-outer">
|
||||
<div class="queue-inner">
|
||||
{% if page.has_other_pages() %}
|
||||
<div class="data-grid-content data-grid-top">
|
||||
{{ page|paginator }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="data-grid-content data-grid-top">
|
||||
{{ page|paginator }}
|
||||
</div>
|
||||
|
||||
{% if tab == 'moderated' %}
|
||||
<div id="reviews-flagged">
|
||||
|
@ -128,11 +126,9 @@
|
|||
<div class="no-results">There are currently no add-ons of this type to review.</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if page.has_other_pages() %}
|
||||
<div class="data-grid-content data-grid-bottom">
|
||||
{{ page|paginator }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="data-grid-content data-grid-bottom">
|
||||
{{ page|paginator }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -23,16 +23,12 @@ def queue_tabnav(context):
|
|||
|
||||
if listed:
|
||||
tabnav = []
|
||||
if acl.action_allowed_for(
|
||||
request.user, amo.permissions.ADDONS_RECOMMENDED_REVIEW
|
||||
):
|
||||
tabnav.append(('recommended', 'queue_recommended', 'Recommended'))
|
||||
if acl.action_allowed_for(request.user, amo.permissions.ADDONS_REVIEW):
|
||||
tabnav.append(
|
||||
(
|
||||
'extension',
|
||||
'queue_extension',
|
||||
'🛠️ Other Pending Review',
|
||||
'🛠️ Manual Review',
|
||||
)
|
||||
)
|
||||
tabnav.append(
|
||||
|
@ -78,11 +74,6 @@ def queue_tabnav(context):
|
|||
else:
|
||||
tabnav = [
|
||||
('all', 'unlisted_queue_all', 'All Unlisted Add-ons'),
|
||||
(
|
||||
'pending_manual_approval',
|
||||
'unlisted_queue_pending_manual_approval',
|
||||
'Unlisted Add-ons Pending Manual Approval',
|
||||
),
|
||||
]
|
||||
|
||||
return tabnav
|
||||
|
|
|
@ -708,7 +708,6 @@ class TestDashboard(TestCase):
|
|||
doc = pq(response.content)
|
||||
assert len(doc('.dashboard h3')) == 9 # All sections are present.
|
||||
expected_links = [
|
||||
reverse('reviewers.queue_recommended'),
|
||||
reverse('reviewers.queue_extension'),
|
||||
reverse('reviewers.reviewlog'),
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide',
|
||||
|
@ -726,7 +725,6 @@ class TestDashboard(TestCase):
|
|||
reverse('reviewers.ratings_moderation_log'),
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide/Moderation',
|
||||
reverse('reviewers.unlisted_queue_all'),
|
||||
reverse('reviewers.unlisted_queue_pending_manual_approval'),
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide',
|
||||
reverse('reviewers.motd'),
|
||||
reverse('reviewers.queue_pending_rejection'),
|
||||
|
@ -734,19 +732,18 @@ class TestDashboard(TestCase):
|
|||
links = [link.attrib['href'] for link in doc('.dashboard a')]
|
||||
assert links == expected_links
|
||||
# pre-approval addons
|
||||
assert doc('.dashboard a')[0].text == 'Recommended (2)'
|
||||
assert doc('.dashboard a')[1].text == 'Other Pending Review (3)'
|
||||
assert doc('.dashboard a')[0].text == 'Manual Review (5)'
|
||||
# auto-approved addons
|
||||
assert doc('.dashboard a')[6].text == 'Auto Approved Add-ons (4)'
|
||||
assert doc('.dashboard a')[5].text == 'Auto Approved Add-ons (4)'
|
||||
# content review
|
||||
assert doc('.dashboard a')[9].text == 'Content Review (11)'
|
||||
assert doc('.dashboard a')[8].text == 'Content Review (11)'
|
||||
# themes
|
||||
assert doc('.dashboard a')[10].text == 'New (1)'
|
||||
assert doc('.dashboard a')[11].text == 'Updates (1)'
|
||||
assert doc('.dashboard a')[9].text == 'New (1)'
|
||||
assert doc('.dashboard a')[10].text == 'Updates (1)'
|
||||
# user ratings moderation
|
||||
assert doc('.dashboard a')[14].text == 'Ratings Awaiting Moderation (1)'
|
||||
assert doc('.dashboard a')[13].text == 'Ratings Awaiting Moderation (1)'
|
||||
# admin tools
|
||||
assert doc('.dashboard a')[21].text == 'Add-ons Pending Rejection (1)'
|
||||
assert doc('.dashboard a')[19].text == 'Add-ons Pending Rejection (1)'
|
||||
|
||||
def test_can_see_all_through_reviewer_view_all_permission(self):
|
||||
self.grant_permission(self.user, 'ReviewerTools:View')
|
||||
|
@ -755,7 +752,6 @@ class TestDashboard(TestCase):
|
|||
doc = pq(response.content)
|
||||
assert len(doc('.dashboard h3')) == 9 # All sections are present.
|
||||
expected_links = [
|
||||
reverse('reviewers.queue_recommended'),
|
||||
reverse('reviewers.queue_extension'),
|
||||
reverse('reviewers.reviewlog'),
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide',
|
||||
|
@ -773,7 +769,6 @@ class TestDashboard(TestCase):
|
|||
reverse('reviewers.ratings_moderation_log'),
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide/Moderation',
|
||||
reverse('reviewers.unlisted_queue_all'),
|
||||
reverse('reviewers.unlisted_queue_pending_manual_approval'),
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide',
|
||||
reverse('reviewers.motd'),
|
||||
reverse('reviewers.queue_pending_rejection'),
|
||||
|
@ -848,7 +843,7 @@ class TestDashboard(TestCase):
|
|||
]
|
||||
links = [link.attrib['href'] for link in doc('.dashboard a')]
|
||||
assert links == expected_links
|
||||
assert doc('.dashboard a')[0].text == 'Other Pending Review (3)'
|
||||
assert doc('.dashboard a')[0].text == 'Manual Review (3)'
|
||||
assert doc('.dashboard a')[5].text == 'Auto Approved Add-ons (1)'
|
||||
|
||||
def test_content_reviewer(self):
|
||||
|
@ -926,7 +921,6 @@ class TestDashboard(TestCase):
|
|||
assert len(doc('.dashboard h3')) == 1
|
||||
expected_links = [
|
||||
reverse('reviewers.unlisted_queue_all'),
|
||||
reverse('reviewers.unlisted_queue_pending_manual_approval'),
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide',
|
||||
]
|
||||
links = [link.attrib['href'] for link in doc('.dashboard a')]
|
||||
|
@ -943,7 +937,6 @@ class TestDashboard(TestCase):
|
|||
assert len(doc('.dashboard h3')) == 9
|
||||
expected_links = [
|
||||
reverse('reviewers.unlisted_queue_all'),
|
||||
reverse('reviewers.unlisted_queue_pending_manual_approval'),
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide',
|
||||
]
|
||||
links = [link.attrib['href'] for link in doc('.dashboard a')]
|
||||
|
@ -952,22 +945,25 @@ class TestDashboard(TestCase):
|
|||
def test_static_theme_reviewer(self):
|
||||
# Create some static themes to test the queue counts.
|
||||
addon_factory(
|
||||
name='Nominated theme',
|
||||
status=amo.STATUS_NOMINATED,
|
||||
type=amo.ADDON_STATICTHEME,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
version_factory(
|
||||
addon=addon_factory(type=amo.ADDON_STATICTHEME),
|
||||
addon=addon_factory(name='Updated theme', type=amo.ADDON_STATICTHEME),
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
version_factory(
|
||||
addon=addon_factory(
|
||||
name='Other updated theme',
|
||||
type=amo.ADDON_STATICTHEME,
|
||||
),
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
)
|
||||
# These two are under admin review and will be ignored.
|
||||
addon_factory(
|
||||
name='Nominated theme under admin review',
|
||||
status=amo.STATUS_NOMINATED,
|
||||
type=amo.ADDON_STATICTHEME,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
|
@ -975,6 +971,7 @@ class TestDashboard(TestCase):
|
|||
)
|
||||
version_factory(
|
||||
addon=addon_factory(
|
||||
name='Updated theme under admin review',
|
||||
type=amo.ADDON_STATICTHEME,
|
||||
reviewer_flags={'needs_admin_theme_review': True},
|
||||
),
|
||||
|
@ -982,6 +979,7 @@ class TestDashboard(TestCase):
|
|||
)
|
||||
# This is an extension so won't be shown
|
||||
addon_factory(
|
||||
name='Nominated extension',
|
||||
status=amo.STATUS_NOMINATED,
|
||||
type=amo.ADDON_EXTENSION,
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
|
@ -1032,7 +1030,7 @@ class TestDashboard(TestCase):
|
|||
]
|
||||
links = [link.attrib['href'] for link in doc('.dashboard a')]
|
||||
assert links == expected_links
|
||||
assert doc('.dashboard a')[0].text == 'Other Pending Review (0)'
|
||||
assert doc('.dashboard a')[0].text == 'Manual Review (0)'
|
||||
assert 'target' not in doc('.dashboard a')[0].attrib
|
||||
assert doc('.dashboard a')[8].text == ('Ratings Awaiting Moderation (0)')
|
||||
assert 'target' not in doc('.dashboard a')[5].attrib
|
||||
|
@ -1141,7 +1139,6 @@ class QueueTest(ReviewerTest):
|
|||
},
|
||||
'version_kw': {
|
||||
'created': self.days_ago(1),
|
||||
'due_date': self.days_ago(-2),
|
||||
'version': '0.1',
|
||||
},
|
||||
'status': amo.STATUS_APPROVED,
|
||||
|
@ -1326,11 +1323,6 @@ class TestQueueBasics(QueueTest):
|
|||
assert response.status_code == 200
|
||||
assert pq(response.content)('.queue-outer .no-results').length == 1
|
||||
|
||||
def test_no_paginator_when_on_single_page(self):
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
assert pq(response.content)('.pagination').length == 0
|
||||
|
||||
def test_paginator_when_many_pages(self):
|
||||
# 'Pending One' and 'Pending Two' should be the only add-ons in
|
||||
# the pending queue, but we'll generate them all for good measure.
|
||||
|
@ -1339,6 +1331,7 @@ class TestQueueBasics(QueueTest):
|
|||
response = self.client.get(self.url, {'per_page': 1})
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content)
|
||||
assert doc('.pagination').length == 2
|
||||
assert doc('.data-grid-top .num-results').text() == ('Results 1\u20131 of 4')
|
||||
assert doc('.data-grid-bottom .num-results').text() == ('Results 1\u20131 of 4')
|
||||
|
||||
|
@ -1392,14 +1385,6 @@ class TestQueueBasics(QueueTest):
|
|||
expected.append(reverse('reviewers.queue_content_review'))
|
||||
assert links == expected
|
||||
|
||||
self.grant_permission(self.user, 'Addons:RecommendedReview')
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content)
|
||||
links = doc('.tabnav li a').map(lambda i, e: e.attrib['href'])
|
||||
expected.insert(0, reverse('reviewers.queue_recommended'))
|
||||
assert links == expected
|
||||
|
||||
self.grant_permission(self.user, 'Reviews:Admin')
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
|
@ -1469,21 +1454,21 @@ class TestThemePendingQueue(QueueTest):
|
|||
super().setUp()
|
||||
# These should be the only ones present.
|
||||
self.expected_addons = self.get_expected_addons_by_names(
|
||||
['Pending One', 'Pending Two']
|
||||
['Pending One', 'Pending Two'], addon_type=amo.ADDON_STATICTHEME
|
||||
)
|
||||
self.expected_versions = self.get_expected_versions(self.expected_addons)
|
||||
Addon.objects.all().update(type=amo.ADDON_STATICTHEME)
|
||||
self.url = reverse('reviewers.queue_theme_pending')
|
||||
GroupUser.objects.filter(user=self.user).delete()
|
||||
self.grant_permission(self.user, 'Addons:ThemeReview')
|
||||
|
||||
def test_results(self):
|
||||
with self.assertNumQueries(10):
|
||||
with self.assertNumQueries(11):
|
||||
# - 2 for savepoints because we're in tests
|
||||
# - 2 for user/groups
|
||||
# - 1 for the current queue count for pagination purposes
|
||||
# - 2 for the addons in the queues and their files (regardless of
|
||||
# how many are in the queue - that's the important bit)
|
||||
# - 3 for the addons in the queues, their versions/files and
|
||||
# translations and their files (regardless of how many are in
|
||||
# the queue - that's the important bit)
|
||||
# - 2 for config items (motd / site notice)
|
||||
# - 1 for my add-ons in user menu
|
||||
self._test_results()
|
||||
|
@ -1494,6 +1479,9 @@ class TestThemePendingQueue(QueueTest):
|
|||
)
|
||||
|
||||
def test_extensions_filtered_out(self):
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addons['Pending Two'], auto_approval_disabled=True
|
||||
)
|
||||
self.addons['Pending Two'].update(type=amo.ADDON_EXTENSION)
|
||||
|
||||
# Extensions shouldn't be shown
|
||||
|
@ -1509,12 +1497,9 @@ class TestThemePendingQueue(QueueTest):
|
|||
class TestExtensionQueue(QueueTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# These should be the only ones present.
|
||||
addons = self.get_expected_addons_by_names(
|
||||
['Pending One', 'Pending Two', 'Nominated One', 'Nominated Two']
|
||||
)
|
||||
self.expected_versions = self.get_expected_versions(addons)
|
||||
self.expected_addons = []
|
||||
# Don't generate add-ons in setUp for this class, its tests are too
|
||||
# different from one another, otherwise it would create duplicate as
|
||||
# the tests each make their own calls to generate what they need.
|
||||
self.url = reverse('reviewers.queue_extension')
|
||||
|
||||
def test_results(self):
|
||||
|
@ -1523,12 +1508,13 @@ class TestExtensionQueue(QueueTest):
|
|||
auto_approve_disabled=True,
|
||||
)
|
||||
self.expected_versions = self.get_expected_versions(self.expected_addons)
|
||||
with self.assertNumQueries(10):
|
||||
with self.assertNumQueries(11):
|
||||
# - 2 for savepoints because we're in tests
|
||||
# - 2 for user/groups
|
||||
# - 1 for the current queue count for pagination purposes
|
||||
# - 2 for the addons in the queues and their files (regardless of
|
||||
# how many are in the queue - that's the important bit)
|
||||
# - 3 for the addons in the queues, their versions/files and
|
||||
# translations and their files (regardless of how many are in
|
||||
# the queue - that's the important bit)
|
||||
# - 2 for config items (motd / site notice)
|
||||
# - 1 for my add-ons in user menu
|
||||
self._test_results()
|
||||
|
@ -1538,6 +1524,7 @@ class TestExtensionQueue(QueueTest):
|
|||
['Pending One', 'Pending Two', 'Nominated One', 'Nominated Two'],
|
||||
auto_approve_disabled=True,
|
||||
)
|
||||
self.expected_versions = self.get_expected_versions(self.expected_addons)
|
||||
version1 = self.addons['Nominated One'].versions.all()[0]
|
||||
version2 = self.addons['Nominated Two'].versions.all()[0]
|
||||
file_ = version2.file
|
||||
|
@ -1579,8 +1566,13 @@ class TestExtensionQueue(QueueTest):
|
|||
)
|
||||
|
||||
def test_queue_layout(self):
|
||||
self.expected_addons = self.get_expected_addons_by_names(
|
||||
['Pending One', 'Pending Two', 'Nominated One', 'Nominated Two'],
|
||||
auto_approve_disabled=True,
|
||||
)
|
||||
self.expected_versions = self.get_expected_versions(self.expected_addons)
|
||||
self._test_queue_layout(
|
||||
'🛠️ Other Pending Review', tab_position=0, total_addons=4, total_queues=4
|
||||
'🛠️ Manual Review', tab_position=0, total_addons=4, total_queues=4
|
||||
)
|
||||
|
||||
def test_webextension_with_auto_approval_disabled_false_filtered_out(self):
|
||||
|
@ -1601,6 +1593,7 @@ class TestExtensionQueue(QueueTest):
|
|||
self._test_results()
|
||||
|
||||
def test_webextension_with_auto_approval_delayed(self):
|
||||
self.generate_files()
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addons['Pending One'],
|
||||
auto_approval_delayed_until=datetime.now() + timedelta(hours=24),
|
||||
|
@ -1611,34 +1604,40 @@ class TestExtensionQueue(QueueTest):
|
|||
)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addons['Pending Two'],
|
||||
auto_approval_delayed_until=datetime.now() - timedelta(hours=24),
|
||||
auto_approval_delayed_until=None,
|
||||
)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addons['Nominated Two'],
|
||||
auto_approval_delayed_until=datetime.now() - timedelta(hours=24),
|
||||
auto_approval_delayed_until=None,
|
||||
)
|
||||
|
||||
self.expected_addons = [
|
||||
self.addons['Nominated One'],
|
||||
self.addons['Pending One'],
|
||||
]
|
||||
self.expected_versions = self.get_expected_versions(self.expected_addons)
|
||||
self._test_results()
|
||||
|
||||
def test_promoted_addon_in_pre_review_group_does_show_up(self):
|
||||
self.generate_files()
|
||||
self.make_addon_promoted(self.addons['Pending One'], group=LINE)
|
||||
self.make_addon_promoted(self.addons['Nominated One'], group=SPOTLIGHT)
|
||||
# STRATEGIC isn't a pre_review group so won't show up
|
||||
self.make_addon_promoted(self.addons['Nominated Two'], group=STRATEGIC)
|
||||
# RECOMMENDED is pre_review, but is handled in it's own queue
|
||||
# RECOMMENDED is pre_review too, it *should* show up
|
||||
self.make_addon_promoted(self.addons['Pending Two'], group=RECOMMENDED)
|
||||
|
||||
self.expected_addons = [
|
||||
self.addons['Nominated One'],
|
||||
self.addons['Pending One'],
|
||||
self.addons['Pending Two'],
|
||||
]
|
||||
# these are the same due_dates we default to in get_files
|
||||
self.expected_versions = self.get_expected_versions(self.expected_addons)
|
||||
# These are the same due_dates we default to in generate_files()
|
||||
# (they were reset since the add-ons were not originally promoted when
|
||||
# created).
|
||||
self.addons['Nominated One'].current_version.update(due_date=self.days_ago(2))
|
||||
self.addons['Pending One'].current_version.update(due_date=self.days_ago(0))
|
||||
self.addons['Pending Two'].current_version.update(due_date=self.days_ago(-1))
|
||||
|
||||
self._test_results()
|
||||
|
||||
|
@ -1690,12 +1689,13 @@ class TestThemeNominatedQueue(QueueTest):
|
|||
self.grant_permission(self.user, 'Addons:ThemeReview')
|
||||
|
||||
def test_results(self):
|
||||
with self.assertNumQueries(10):
|
||||
with self.assertNumQueries(11):
|
||||
# - 2 for savepoints because we're in tests
|
||||
# - 2 for user/groups
|
||||
# - 1 for the current queue count for pagination purposes
|
||||
# - 2 for the addons in the queues and their files (regardless of
|
||||
# how many are in the queue - that's the important bit)
|
||||
# - 3 for the addons in the queues, their versions/files and
|
||||
# translations and their files (regardless of how many are in
|
||||
# the queue - that's the important bit)
|
||||
# - 2 for config items (motd / site notice)
|
||||
# - 1 for my add-ons in user menu
|
||||
self._test_results()
|
||||
|
@ -1757,88 +1757,6 @@ class TestThemeNominatedQueue(QueueTest):
|
|||
self._test_results()
|
||||
|
||||
|
||||
class TestRecommendedQueue(QueueTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.grant_permission(self.user, 'Addons:RecommendedReview')
|
||||
# These should be the only ones present.
|
||||
self.expected_addons = self.get_expected_addons_by_names(
|
||||
['Pending One', 'Pending Two', 'Nominated One', 'Nominated Two']
|
||||
)
|
||||
self.expected_versions = self.get_expected_versions(self.expected_addons)
|
||||
due_date_counter = 2
|
||||
for addon in self.expected_addons:
|
||||
self.make_addon_promoted(addon, RECOMMENDED)
|
||||
# these are the same due_dates we default to in get_files
|
||||
addon.current_version.update(due_date=self.days_ago(due_date_counter))
|
||||
due_date_counter -= 1
|
||||
self.url = reverse('reviewers.queue_recommended')
|
||||
|
||||
def test_results(self):
|
||||
with self.assertNumQueries(10):
|
||||
# - 2 for savepoints because we're in tests
|
||||
# - 2 for user/groups
|
||||
# - 1 for the current queue count for pagination purposes
|
||||
# - 2 for the addons in the queues and their files (regardless of
|
||||
# how many are in the queue - that's the important bit)
|
||||
# - 2 for config items (motd / site notice)
|
||||
# - 1 for my add-ons in user menu
|
||||
self._test_results()
|
||||
|
||||
def test_results_two_versions(self):
|
||||
version1 = self.addons['Nominated One'].versions.all()[0]
|
||||
version2 = self.addons['Nominated Two'].versions.all()[0]
|
||||
file_ = version2.file
|
||||
|
||||
# Create another version for Nominated Two, v0.2, by "cloning" v0.1.
|
||||
# Its creation date must be more recent than v0.1 for version ordering
|
||||
# to work. Its due date must be coherent with that, but also
|
||||
# not cause the queue order to change with respect to the other
|
||||
# add-ons.
|
||||
version2.created = version2.created + timedelta(minutes=1)
|
||||
version2.due_date = version2.due_date + timedelta(minutes=1)
|
||||
version2.pk = None
|
||||
version2.version = '0.2'
|
||||
version2.save()
|
||||
|
||||
# Associate v0.2 it with a file.
|
||||
file_.pk = None
|
||||
file_.version = version2
|
||||
file_.save()
|
||||
|
||||
# disable old files like Version.from_upload() would.
|
||||
version2.disable_old_files()
|
||||
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
expected = [
|
||||
(
|
||||
'Nominated One 0.1',
|
||||
reverse('reviewers.review', args=[version1.addon.pk]),
|
||||
),
|
||||
(
|
||||
'Nominated Two 0.2',
|
||||
reverse('reviewers.review', args=[version2.addon.pk]),
|
||||
),
|
||||
]
|
||||
doc = pq(response.content)
|
||||
check_links(
|
||||
expected, doc('#addon-queue tr.addon-row td a:not(.app-icon)'), verify=False
|
||||
)
|
||||
|
||||
def test_queue_layout(self):
|
||||
self._test_queue_layout(
|
||||
'Recommended', tab_position=0, total_addons=4, total_queues=5
|
||||
)
|
||||
|
||||
def test_nothing_recommended_filtered_out(self):
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addons['Pending Two'], auto_approval_disabled=False
|
||||
)
|
||||
|
||||
self._test_results()
|
||||
|
||||
|
||||
class TestModeratedQueue(QueueTest):
|
||||
fixtures = ['base/users', 'ratings/dev-reply']
|
||||
|
||||
|
@ -2090,70 +2008,6 @@ class TestUnlistedAllList(QueueTest):
|
|||
assert json.loads(response.content) == {'reviewtext': 'stish goin` down son'}
|
||||
|
||||
|
||||
class TestUnlistedPendingManualApproval(QueueTest):
|
||||
listed = False
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.url = reverse('reviewers.unlisted_queue_pending_manual_approval')
|
||||
self.generate_files(auto_approve_disabled=True)
|
||||
self.expected_addons = [
|
||||
self.addons['Pending One'],
|
||||
self.addons['Nominated Two'],
|
||||
self.addons['Pending Two'],
|
||||
self.addons['Nominated One'],
|
||||
]
|
||||
self.expected_versions = self.get_expected_versions(self.expected_addons)
|
||||
for i, addon in enumerate(self.expected_addons):
|
||||
AutoApprovalSummary.objects.create(
|
||||
version=addon.versions.latest('pk'), score=100 - i
|
||||
)
|
||||
# Set one of the add-ons as needing an admin, regular reviewer
|
||||
# shouldn't see it in their queue.
|
||||
self.reserved_addon = self.expected_addons.pop()
|
||||
self.reserved_addon.reviewerflags.update(needs_admin_code_review=True)
|
||||
|
||||
def test_results(self):
|
||||
with self.assertNumQueries(10):
|
||||
# - 2 for savepoints because we're in tests
|
||||
# - 2 for user/groups
|
||||
# - 1 for the current queue count for pagination purposes
|
||||
# - 2 for config items (motd / site notice)
|
||||
# - 1 for my add-ons in user menu
|
||||
# - 1 main queue query
|
||||
# - 1 translations
|
||||
self._test_results()
|
||||
|
||||
def test_results_admin_reviewer(self):
|
||||
self.client.force_login(UserProfile.objects.get(email='admin@mozilla.com'))
|
||||
# Add back the add-on set as needing an admin code review to the
|
||||
# expected list since we are now an admin reviewer.
|
||||
self.expected_addons.append(self.reserved_addon)
|
||||
self.test_results()
|
||||
self.test_queue_layout()
|
||||
|
||||
def test_queue_layout(self):
|
||||
self._test_queue_layout(
|
||||
'Unlisted Add-ons Pending Manual Approval',
|
||||
tab_position=1,
|
||||
total_addons=len(self.expected_addons),
|
||||
total_queues=2,
|
||||
per_page=1,
|
||||
)
|
||||
|
||||
def test_only_viewable_with_specific_permission(self):
|
||||
# Regular addon reviewer does not have access.
|
||||
self.user.groupuser_set.all().delete() # Remove all permissions
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 403
|
||||
|
||||
# Regular user doesn't have access.
|
||||
self.client.logout()
|
||||
self.client.force_login(UserProfile.objects.get(email='regular@mozilla.com'))
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
class TestAutoApprovedQueue(QueueTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
@ -2644,9 +2498,9 @@ class TestScannersReviewQueue(QueueTest):
|
|||
|
||||
self._test_queue_layout(
|
||||
'Versions Needing Human Review',
|
||||
tab_position=2,
|
||||
tab_position=1,
|
||||
total_addons=4,
|
||||
total_queues=10,
|
||||
total_queues=9,
|
||||
per_page=1,
|
||||
)
|
||||
|
||||
|
|
|
@ -11,11 +11,6 @@ urlpatterns = (
|
|||
re_path(
|
||||
r'^dashboard$', lambda request: redirect('reviewers.dashboard', permanent=True)
|
||||
),
|
||||
re_path(
|
||||
r'^queue/recommended$',
|
||||
views.queue_recommended,
|
||||
name='reviewers.queue_recommended',
|
||||
),
|
||||
re_path(
|
||||
r'^queue/extension$', views.queue_extension, name='reviewers.queue_extension'
|
||||
),
|
||||
|
@ -58,11 +53,6 @@ urlpatterns = (
|
|||
views.unlisted_list,
|
||||
name='reviewers.unlisted_queue_all',
|
||||
),
|
||||
re_path(
|
||||
r'^unlisted_queue/pending_manual_approval$',
|
||||
views.unlisted_pending_manual_approval,
|
||||
name='reviewers.unlisted_queue_pending_manual_approval',
|
||||
),
|
||||
re_path(
|
||||
r'^moderationlog$',
|
||||
views.ratings_moderation_log,
|
||||
|
|
|
@ -119,9 +119,12 @@ class AddonQueueTable(tables.Table, ItemStateTable):
|
|||
)
|
||||
orderable = False
|
||||
|
||||
def get_version(self, record):
|
||||
return record.current_version
|
||||
|
||||
def render_flags(self, record):
|
||||
if not hasattr(record, 'flags'):
|
||||
record.flags = get_flags(record, record.current_version)
|
||||
record.flags = get_flags(record, self.get_version(record))
|
||||
return markupsafe.Markup(
|
||||
''.join(
|
||||
'<div class="app-icon ed-sprite-%s" title="%s"></div>' % flag
|
||||
|
@ -130,16 +133,20 @@ class AddonQueueTable(tables.Table, ItemStateTable):
|
|||
)
|
||||
|
||||
def _get_addon_name_url(self, record):
|
||||
return reverse('reviewers.review', args=[record.id])
|
||||
args = [record.id]
|
||||
if self.get_version(record).channel == amo.CHANNEL_UNLISTED:
|
||||
args.insert(0, 'unlisted')
|
||||
return reverse('reviewers.review', args=args)
|
||||
|
||||
def render_addon_name(self, record):
|
||||
url = self._get_addon_name_url(record)
|
||||
self.increment_item()
|
||||
return markupsafe.Markup(
|
||||
'<a href="%s">%s <em>%s</em></a>'
|
||||
% (
|
||||
url,
|
||||
markupsafe.escape(record.name),
|
||||
markupsafe.escape(record.current_version),
|
||||
markupsafe.escape(self.get_version(record).version),
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -151,7 +158,9 @@ class AddonQueueTable(tables.Table, ItemStateTable):
|
|||
'<span title="%s">%d</span>'
|
||||
% (
|
||||
'\n'.join(
|
||||
record.current_version.autoapprovalsummary.get_pretty_weight_info()
|
||||
self.get_version(
|
||||
record
|
||||
).autoapprovalsummary.get_pretty_weight_info()
|
||||
),
|
||||
value,
|
||||
)
|
||||
|
@ -165,7 +174,7 @@ class AddonQueueTable(tables.Table, ItemStateTable):
|
|||
|
||||
class PendingManualApprovalQueueTable(AddonQueueTable):
|
||||
addon_type = tables.Column(verbose_name='Type', accessor='type', orderable=False)
|
||||
due_date = tables.Column(verbose_name='Due Date', accessor='first_version_due')
|
||||
due_date = tables.Column(verbose_name='Due Date', accessor='first_version_due_date')
|
||||
|
||||
class Meta:
|
||||
fields = ('addon_name', 'addon_type', 'due_date', 'flags')
|
||||
|
@ -179,32 +188,21 @@ class PendingManualApprovalQueueTable(AddonQueueTable):
|
|||
|
||||
@classmethod
|
||||
def get_queryset(cls, admin_reviewer=False):
|
||||
return Addon.objects.get_listed_pending_manual_approval_queue(
|
||||
admin_reviewer=admin_reviewer
|
||||
)
|
||||
return Addon.objects.get_pending_review_queue(admin_reviewer=admin_reviewer)
|
||||
|
||||
def _get_due_date(self, record):
|
||||
return record.first_version_due
|
||||
|
||||
def render_addon_name(self, record):
|
||||
url = self._get_addon_name_url(record)
|
||||
self.increment_item()
|
||||
return markupsafe.Markup(
|
||||
'<a href="%s">%s <em>%s</em></a>'
|
||||
% (
|
||||
url,
|
||||
markupsafe.escape(record.name),
|
||||
markupsafe.escape(getattr(record, 'latest_version', '')),
|
||||
)
|
||||
)
|
||||
def get_version(self, record):
|
||||
# Use the property set by get_pending_review_queue() to display the
|
||||
# right version.
|
||||
return record.first_pending_version
|
||||
|
||||
def render_addon_type(self, record):
|
||||
return record.get_type_display()
|
||||
|
||||
def render_due_date(self, record):
|
||||
due_date = self.get_version(record).due_date
|
||||
return markupsafe.Markup(
|
||||
f'<span title="{markupsafe.escape(self._get_due_date(record))}">'
|
||||
f'{markupsafe.escape(naturaltime(self._get_due_date(record)))}</span>'
|
||||
f'<span title="{markupsafe.escape(due_date)}">'
|
||||
f'{markupsafe.escape(naturaltime(due_date))}</span>'
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
@ -214,71 +212,24 @@ class PendingManualApprovalQueueTable(AddonQueueTable):
|
|||
return 'due_date'
|
||||
|
||||
|
||||
class RecommendedPendingManualApprovalQueueTable(PendingManualApprovalQueueTable):
|
||||
@classmethod
|
||||
def get_queryset(cls, admin_reviewer=False):
|
||||
return Addon.objects.get_listed_pending_manual_approval_queue(
|
||||
admin_reviewer=admin_reviewer, recommendable=True
|
||||
)
|
||||
|
||||
|
||||
class NewThemesQueueTable(PendingManualApprovalQueueTable):
|
||||
@classmethod
|
||||
def get_queryset(cls, admin_reviewer=False):
|
||||
return Addon.objects.get_listed_pending_manual_approval_queue(
|
||||
return Addon.objects.get_themes_pending_manual_approval_queue(
|
||||
admin_reviewer=admin_reviewer,
|
||||
statuses=(amo.STATUS_NOMINATED,),
|
||||
types=amo.GROUP_TYPE_THEME,
|
||||
)
|
||||
|
||||
|
||||
class UpdatedThemesQueueTable(NewThemesQueueTable):
|
||||
@classmethod
|
||||
def get_queryset(cls, admin_reviewer=False):
|
||||
return Addon.objects.get_listed_pending_manual_approval_queue(
|
||||
return Addon.objects.get_themes_pending_manual_approval_queue(
|
||||
admin_reviewer=admin_reviewer,
|
||||
statuses=(amo.STATUS_APPROVED,),
|
||||
types=amo.GROUP_TYPE_THEME,
|
||||
)
|
||||
|
||||
|
||||
class UnlistedPendingManualApprovalQueueTable(PendingManualApprovalQueueTable):
|
||||
created = tables.Column(verbose_name='Created', accessor='first_version_created')
|
||||
score = tables.Column(verbose_name='Maliciousness Score', accessor='worst_score')
|
||||
show_count_in_dashboard = False
|
||||
|
||||
class Meta(PendingManualApprovalQueueTable.Meta):
|
||||
fields = (
|
||||
'addon_name',
|
||||
'addon_type',
|
||||
'created',
|
||||
'score',
|
||||
)
|
||||
exclude = (
|
||||
'last_human_review',
|
||||
'code_weight',
|
||||
'metadata_weight',
|
||||
'weight',
|
||||
'flags',
|
||||
)
|
||||
|
||||
def _get_addon_name_url(self, record):
|
||||
return reverse('reviewers.review', args=['unlisted', record.id])
|
||||
|
||||
def _get_created(self, record):
|
||||
return record.first_version_created
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, admin_reviewer=False):
|
||||
return Addon.objects.get_unlisted_pending_manual_approval_queue(
|
||||
admin_reviewer=admin_reviewer
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def default_order_by(cls):
|
||||
return '-score'
|
||||
|
||||
|
||||
class PendingRejectionTable(AddonQueueTable):
|
||||
deadline = tables.Column(
|
||||
verbose_name='Pending Rejection Deadline',
|
||||
|
|
|
@ -100,10 +100,8 @@ from olympia.reviewers.utils import (
|
|||
NewThemesQueueTable,
|
||||
PendingManualApprovalQueueTable,
|
||||
PendingRejectionTable,
|
||||
RecommendedPendingManualApprovalQueueTable,
|
||||
ReviewHelper,
|
||||
HumanReviewTable,
|
||||
UnlistedPendingManualApprovalQueueTable,
|
||||
UpdatedThemesQueueTable,
|
||||
ViewUnlistedAllListTable,
|
||||
)
|
||||
|
@ -196,32 +194,17 @@ def dashboard(request):
|
|||
queue_counts = fetch_queue_counts(admin_reviewer=admin_reviewer)
|
||||
|
||||
if view_all or acl.action_allowed_for(request.user, amo.permissions.ADDONS_REVIEW):
|
||||
sections['Pre-Review Add-ons'] = []
|
||||
if view_all or acl.action_allowed_for(
|
||||
request.user, amo.permissions.ADDONS_RECOMMENDED_REVIEW
|
||||
):
|
||||
sections['Pre-Review Add-ons'].append(
|
||||
(
|
||||
'Recommended ({0})'.format(queue_counts['recommended']),
|
||||
reverse('reviewers.queue_recommended'),
|
||||
)
|
||||
)
|
||||
sections['Pre-Review Add-ons'].extend(
|
||||
sections['Manual Review'] = [
|
||||
(
|
||||
(
|
||||
'Other Pending Review ({0})'.format(queue_counts['extension']),
|
||||
reverse('reviewers.queue_extension'),
|
||||
),
|
||||
(
|
||||
'Review Log',
|
||||
reverse('reviewers.reviewlog'),
|
||||
),
|
||||
(
|
||||
'Add-on Review Guide',
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide',
|
||||
),
|
||||
)
|
||||
)
|
||||
'Manual Review ({0})'.format(queue_counts['extension']),
|
||||
reverse('reviewers.queue_extension'),
|
||||
),
|
||||
('Review Log', reverse('reviewers.reviewlog')),
|
||||
(
|
||||
'Add-on Review Guide',
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide',
|
||||
),
|
||||
]
|
||||
sections['Human Review Needed'] = [
|
||||
(
|
||||
'Versions Needing Human Review',
|
||||
|
@ -302,10 +285,6 @@ def dashboard(request):
|
|||
('All Unlisted Add-ons'),
|
||||
reverse('reviewers.unlisted_queue_all'),
|
||||
),
|
||||
(
|
||||
'Unlisted Add-ons Pending Manual Approval',
|
||||
reverse('reviewers.unlisted_queue_pending_manual_approval'),
|
||||
),
|
||||
(
|
||||
'Review Guide',
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide',
|
||||
|
@ -416,7 +395,6 @@ def _queue(request, tab, unlisted=False):
|
|||
|
||||
reviewer_tables_registry = {
|
||||
'extension': PendingManualApprovalQueueTable,
|
||||
'recommended': RecommendedPendingManualApprovalQueueTable,
|
||||
'theme_pending': UpdatedThemesQueueTable,
|
||||
'theme_nominated': NewThemesQueueTable,
|
||||
'auto_approved': AutoApprovedTable,
|
||||
|
@ -424,7 +402,6 @@ reviewer_tables_registry = {
|
|||
'mad': MadReviewTable,
|
||||
'human_review': HumanReviewTable,
|
||||
'pending_rejection': PendingRejectionTable,
|
||||
'unlisted_pending_manual_approval': UnlistedPendingManualApprovalQueueTable,
|
||||
'unlisted': ViewUnlistedAllListTable,
|
||||
}
|
||||
|
||||
|
@ -461,11 +438,6 @@ def queue_extension(request):
|
|||
return _queue(request, 'extension')
|
||||
|
||||
|
||||
@permission_or_tools_listed_view_required(amo.permissions.ADDONS_RECOMMENDED_REVIEW)
|
||||
def queue_recommended(request):
|
||||
return _queue(request, 'recommended')
|
||||
|
||||
|
||||
@permission_or_tools_listed_view_required(amo.permissions.STATIC_THEMES_REVIEW)
|
||||
def queue_theme_nominated(request):
|
||||
return _queue(request, 'theme_nominated')
|
||||
|
@ -1046,15 +1018,6 @@ def unlisted_list(request):
|
|||
)
|
||||
|
||||
|
||||
@permission_or_tools_unlisted_view_required(amo.permissions.ADDONS_REVIEW_UNLISTED)
|
||||
def unlisted_pending_manual_approval(request):
|
||||
return _queue(
|
||||
request,
|
||||
'unlisted_pending_manual_approval',
|
||||
unlisted=True,
|
||||
)
|
||||
|
||||
|
||||
def policy_viewer(request, addon, eula_or_privacy, page_title, long_title):
|
||||
unlisted_only = not addon.has_listed_versions(include_deleted=True)
|
||||
if unlisted_only and not acl.is_unlisted_addons_viewer_or_reviewer(request.user):
|
||||
|
|
|
@ -29,6 +29,12 @@ def _delay_auto_approval(version):
|
|||
addon=version.addon,
|
||||
defaults={'auto_approval_delayed_until': in_twenty_four_hours},
|
||||
)
|
||||
# When introducing a short auto-approval delay, reset the due date to match
|
||||
# the delay, unless it's already set to before the delay expires. That way
|
||||
# reviewers are incentivized to look at those versions before they go back
|
||||
# to being auto-approved.
|
||||
due_date = min(version.due_date or in_twenty_four_hours, in_twenty_four_hours)
|
||||
version.reset_due_date(due_date=due_date)
|
||||
|
||||
|
||||
def _delay_auto_approval_indefinitely(version):
|
||||
|
|
|
@ -56,7 +56,7 @@ class TestActions(TestCase):
|
|||
assert version.needs_human_review
|
||||
|
||||
def test_delay_auto_approval(self):
|
||||
addon = addon_factory()
|
||||
addon = addon_factory(file_kw={'status': amo.STATUS_AWAITING_REVIEW})
|
||||
version = addon.current_version
|
||||
assert not version.needs_human_review
|
||||
assert addon.auto_approval_delayed_until is None
|
||||
|
@ -66,9 +66,60 @@ class TestActions(TestCase):
|
|||
now=datetime.now() + timedelta(hours=24),
|
||||
)
|
||||
assert version.needs_human_review
|
||||
self.assertCloseToNow(
|
||||
version.due_date,
|
||||
now=datetime.now() + timedelta(hours=24),
|
||||
)
|
||||
|
||||
def test_delay_auto_approval_existing_due_date_older(self):
|
||||
addon = addon_factory(
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
reviewer_flags={'auto_approval_disabled': True},
|
||||
version_kw={'due_date': self.days_ago(1)},
|
||||
)
|
||||
version = addon.current_version
|
||||
self.assertCloseToNow(version.due_date, now=self.days_ago(1))
|
||||
assert not version.needs_human_review
|
||||
assert addon.auto_approval_delayed_until is None
|
||||
_delay_auto_approval(version)
|
||||
addon.reviewerflags.reload()
|
||||
self.assertCloseToNow(
|
||||
addon.auto_approval_delayed_until,
|
||||
now=datetime.now() + timedelta(hours=24),
|
||||
)
|
||||
assert version.needs_human_review
|
||||
# We kept the original due date as it's shorter.
|
||||
self.assertCloseToNow(version.due_date, now=self.days_ago(1))
|
||||
|
||||
def test_delay_auto_approval_existing_due_date_newer(self):
|
||||
addon = addon_factory(
|
||||
file_kw={'status': amo.STATUS_AWAITING_REVIEW},
|
||||
reviewer_flags={'auto_approval_disabled': True},
|
||||
version_kw={'due_date': datetime.now() + timedelta(hours=72)},
|
||||
)
|
||||
version = addon.current_version
|
||||
self.assertCloseToNow(
|
||||
version.due_date,
|
||||
now=datetime.now() + timedelta(hours=72),
|
||||
)
|
||||
assert not version.needs_human_review
|
||||
assert addon.auto_approval_delayed_until is None
|
||||
_delay_auto_approval(version)
|
||||
addon.reviewerflags.reload()
|
||||
self.assertCloseToNow(
|
||||
addon.auto_approval_delayed_until,
|
||||
now=datetime.now() + timedelta(hours=24),
|
||||
)
|
||||
assert version.needs_human_review
|
||||
# We overrode the due date with 24 hours so that it goes back to the
|
||||
# top of the queue.
|
||||
self.assertCloseToNow(
|
||||
version.due_date,
|
||||
now=datetime.now() + timedelta(hours=24),
|
||||
)
|
||||
|
||||
def test_delay_auto_approval_indefinitely(self):
|
||||
addon = addon_factory()
|
||||
addon = addon_factory(file_kw={'status': amo.STATUS_AWAITING_REVIEW})
|
||||
version = addon.current_version
|
||||
assert not version.needs_human_review
|
||||
assert addon.auto_approval_delayed_until is None
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 3.2.16 on 2023-01-18 13:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('versions', '0032_auto_20230107_0048'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='version',
|
||||
name='nomination',
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='version',
|
||||
index=models.Index(fields=['due_date'], name='versions_due_date_b9c73ed7'),
|
||||
),
|
||||
]
|
|
@ -1,5 +1,5 @@
|
|||
import datetime
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from base64 import b64encode
|
||||
from urllib.parse import urlparse
|
||||
|
@ -299,6 +299,7 @@ class Version(OnChangeMixin, ModelBase):
|
|||
indexes = [
|
||||
models.Index(fields=('addon',), name='addon_id'),
|
||||
models.Index(fields=('license',), name='license_id'),
|
||||
models.Index(fields=('due_date',), name='versions_due_date_b9c73ed7'),
|
||||
]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
|
@ -515,7 +516,7 @@ class Version(OnChangeMixin, ModelBase):
|
|||
# Track the time it took from first upload through validation
|
||||
# (and whatever else) until a version was created.
|
||||
upload_start = utc_millesecs_from_epoch(upload.created)
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.now()
|
||||
now_ts = utc_millesecs_from_epoch(now)
|
||||
upload_time = now_ts - upload_start
|
||||
|
||||
|
|
|
@ -874,10 +874,11 @@ class TestVersion(TestCase):
|
|||
)
|
||||
assert version.should_have_due_date
|
||||
|
||||
# auto_approval_delayed_until addons should also have a due_date
|
||||
# If auto_approval_delayed_until is present it should also have a
|
||||
# due_date
|
||||
addon.reviewerflags.update(
|
||||
auto_approval_disabled_until_next_approval=False,
|
||||
auto_approval_delayed_until=datetime.now(),
|
||||
auto_approval_delayed_until=datetime.now() + timedelta(hours=1),
|
||||
)
|
||||
assert version.should_have_due_date
|
||||
|
||||
|
@ -935,10 +936,11 @@ class TestVersion(TestCase):
|
|||
)
|
||||
assert version.should_have_due_date
|
||||
|
||||
# auto_approval_delayed_until addons should also have a due_date
|
||||
# If auto_approval_delayed_until is present it should also have a
|
||||
# due_date
|
||||
addon.reviewerflags.update(
|
||||
auto_approval_disabled_until_next_approval_unlisted=False,
|
||||
auto_approval_delayed_until=datetime.now(),
|
||||
auto_approval_delayed_until=datetime.now() + timedelta(hours=1),
|
||||
)
|
||||
assert version.should_have_due_date
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче