Merge pre-review queues together (#20181)

* Merge pre-review queues together
This commit is contained in:
Mathieu Pillard 2023-01-23 14:42:57 +01:00 коммит произвёл GitHub
Родитель 45d3ee8099
Коммит 01e5260ff2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 443 добавлений и 705 удалений

Просмотреть файл

@ -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">&hellip;</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">&hellip;</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>&ndash;<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">&hellip;</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">&hellip;</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>&ndash;<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