Remove views/code about info requests (#15168)
* Remove views/code about info requests Info requests are no longer possible, replaced with delayed rejections (or simple reviewer reply with no deadline). * Move migration * Remove `request` from VersionForm, it's no longer needed
This commit is contained in:
Родитель
8df81b9be4
Коммит
66348f6b01
|
@ -85,7 +85,6 @@ add-on.
|
|||
:>json boolean needs_admin_code_review: Boolean indicating whether the add-on needs its code to be reviewed by an admin or not.
|
||||
:>json boolean needs_admin_content_review: Boolean indicating whether the add-on needs its content to be reviewed by an admin or not.
|
||||
:>json boolean needs_admin_theme_review: Boolean indicating whether the theme needs to be reviewed by an admin or not.
|
||||
:>json string|null pending_info_request: Deadline date for the pending info request as a string, or ``null``.
|
||||
|
||||
------------------
|
||||
Allow resubmission
|
||||
|
|
|
@ -9,7 +9,7 @@ Hello,
|
|||
|
||||
*********
|
||||
|
||||
To respond, please reply to this email or visit {{ url }}. {% if is_info_request and number_of_days_left %}If we do not hear from you within {{ number_of_days_left }} of this notification, this listing may be removed from addons.mozilla.org.{% endif %}
|
||||
To respond, please reply to this email or visit {{ url }}.
|
||||
|
||||
|
||||
Thank you for your attention.
|
||||
|
|
|
@ -3,7 +3,6 @@ import copy
|
|||
import json
|
||||
import os
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from email.utils import formataddr
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -23,7 +22,6 @@ from olympia.activity.utils import (
|
|||
ActivityEmailTokenError, ActivityEmailUUIDError, add_email_to_activity_log,
|
||||
add_email_to_activity_log_wrapper, log_and_notify,
|
||||
notify_about_activity_log, NOTIFICATIONS_FROM_EMAIL, send_activity_mail)
|
||||
from olympia.addons.models import Addon, AddonReviewerFlags
|
||||
from olympia.amo.templatetags.jinja_helpers import absolutify
|
||||
from olympia.amo.tests import TestCase, addon_factory, user_factory
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
|
@ -280,21 +278,6 @@ class TestLogAndNotify(TestCase):
|
|||
assert reply_to.endswith(settings.INBOUND_EMAIL_DOMAIN)
|
||||
return recipients
|
||||
|
||||
def _check_email_info_request(self, call, url, reason_text, days_text):
|
||||
subject = call[0][0]
|
||||
body = call[0][1]
|
||||
assert subject == u'Mozilla Add-ons: Action Required for %s %s' % (
|
||||
self.addon.name, self.version.version)
|
||||
assert ('visit %s' % url) in body
|
||||
assert ('receiving this email because %s' % reason_text) in body
|
||||
if days_text is not None:
|
||||
assert 'If we do not hear from you within' in body
|
||||
assert days_text in body
|
||||
assert 'reviewing version %s of the add-on %s' % (
|
||||
self.version.version, self.addon.name) in body
|
||||
assert self.reviewer.name not in body
|
||||
assert self.reviewer.reviewer_name in body
|
||||
|
||||
def _check_email(self, call, url, reason_text):
|
||||
subject = call[0][0]
|
||||
body = call[0][1]
|
||||
|
@ -305,118 +288,8 @@ class TestLogAndNotify(TestCase):
|
|||
assert 'If we do not hear from you within' not in body
|
||||
assert self.reviewer.name not in body
|
||||
|
||||
@mock.patch('olympia.activity.utils.send_mail')
|
||||
def test_reviewer_request_for_information(self, send_mail_mock):
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addon,
|
||||
pending_info_request=datetime.now() + timedelta(days=14))
|
||||
self._create(amo.LOG.REQUEST_INFORMATION, self.reviewer)
|
||||
log_and_notify(
|
||||
amo.LOG.REQUEST_INFORMATION, 'blah', self.reviewer, self.version)
|
||||
|
||||
assert send_mail_mock.call_count == 2 # Both authors.
|
||||
sender = formataddr(
|
||||
(self.reviewer.reviewer_name, NOTIFICATIONS_FROM_EMAIL))
|
||||
assert sender == send_mail_mock.call_args_list[0][1]['from_email']
|
||||
recipients = self._recipients(send_mail_mock)
|
||||
assert len(recipients) == 2
|
||||
assert self.developer.email in recipients
|
||||
assert self.developer2.email in recipients
|
||||
# The reviewer who sent it doesn't get their email back.
|
||||
assert self.reviewer.email not in recipients
|
||||
|
||||
self._check_email_info_request(
|
||||
send_mail_mock.call_args_list[0],
|
||||
absolutify(self.addon.get_dev_url('versions')),
|
||||
'you are listed as an author of this add-on.',
|
||||
'14 days of this notification')
|
||||
self._check_email_info_request(
|
||||
send_mail_mock.call_args_list[1],
|
||||
absolutify(self.addon.get_dev_url('versions')),
|
||||
'you are listed as an author of this add-on.',
|
||||
'14 days of this notification')
|
||||
|
||||
@mock.patch('olympia.activity.utils.send_mail')
|
||||
def test_reviewer_request_for_information_close_date(self, send_mail_mock):
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addon,
|
||||
pending_info_request=datetime.now() + timedelta(days=1))
|
||||
self._create(amo.LOG.REQUEST_INFORMATION, self.reviewer)
|
||||
log_and_notify(
|
||||
amo.LOG.REQUEST_INFORMATION, 'blah', self.reviewer, self.version)
|
||||
|
||||
assert send_mail_mock.call_count == 2 # Both authors.
|
||||
sender = formataddr(
|
||||
(self.reviewer.reviewer_name, NOTIFICATIONS_FROM_EMAIL))
|
||||
assert sender == send_mail_mock.call_args_list[0][1]['from_email']
|
||||
recipients = self._recipients(send_mail_mock)
|
||||
assert len(recipients) == 2
|
||||
assert self.developer.email in recipients
|
||||
assert self.developer2.email in recipients
|
||||
# The reviewer who sent it doesn't get their email back.
|
||||
assert self.reviewer.email not in recipients
|
||||
|
||||
self._check_email_info_request(
|
||||
send_mail_mock.call_args_list[0],
|
||||
absolutify(self.addon.get_dev_url('versions')),
|
||||
'you are listed as an author of this add-on.',
|
||||
'one (1) day of this notification')
|
||||
self._check_email_info_request(
|
||||
send_mail_mock.call_args_list[1],
|
||||
absolutify(self.addon.get_dev_url('versions')),
|
||||
'you are listed as an author of this add-on.',
|
||||
'one (1) day of this notification')
|
||||
|
||||
@mock.patch('olympia.activity.utils.send_mail')
|
||||
def test_reviewer_request_for_information_far_date(self, send_mail_mock):
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addon,
|
||||
pending_info_request=datetime.now() + timedelta(days=21))
|
||||
self._create(amo.LOG.REQUEST_INFORMATION, self.reviewer)
|
||||
log_and_notify(
|
||||
amo.LOG.REQUEST_INFORMATION, 'blah', self.reviewer, self.version)
|
||||
|
||||
assert send_mail_mock.call_count == 2 # Both authors.
|
||||
sender = formataddr(
|
||||
(self.reviewer.reviewer_name, NOTIFICATIONS_FROM_EMAIL))
|
||||
assert sender == send_mail_mock.call_args_list[0][1]['from_email']
|
||||
recipients = self._recipients(send_mail_mock)
|
||||
assert len(recipients) == 2
|
||||
assert self.developer.email in recipients
|
||||
assert self.developer2.email in recipients
|
||||
# The reviewer who sent it doesn't get their email back.
|
||||
assert self.reviewer.email not in recipients
|
||||
|
||||
self._check_email_info_request(
|
||||
send_mail_mock.call_args_list[0],
|
||||
absolutify(self.addon.get_dev_url('versions')),
|
||||
'you are listed as an author of this add-on.',
|
||||
'21 days of this notification')
|
||||
self._check_email_info_request(
|
||||
send_mail_mock.call_args_list[1],
|
||||
absolutify(self.addon.get_dev_url('versions')),
|
||||
'you are listed as an author of this add-on.',
|
||||
'21 days of this notification')
|
||||
|
||||
def test_post_reviewer_request_for_information(self):
|
||||
GroupUser.objects.filter(user=self.reviewer).delete()
|
||||
self.grant_permission(
|
||||
self.reviewer, 'Addons:PostReview', 'Reviewers: Foo')
|
||||
self.test_reviewer_request_for_information()
|
||||
|
||||
def test_content_reviewer_request_for_information(self):
|
||||
GroupUser.objects.filter(user=self.reviewer).delete()
|
||||
self.grant_permission(
|
||||
self.reviewer, 'Addons:ContentReview', 'Reviewers: Bar')
|
||||
self.test_reviewer_request_for_information()
|
||||
|
||||
@mock.patch('olympia.activity.utils.send_mail')
|
||||
def test_developer_reply(self, send_mail_mock):
|
||||
# Set pending info request flag to make sure
|
||||
# it has been dropped after the reply.
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addon,
|
||||
pending_info_request=datetime.now() + timedelta(days=1))
|
||||
# One from the reviewer.
|
||||
self._create(amo.LOG.REJECT_VERSION, self.reviewer)
|
||||
# One from the developer. So the developer is on the 'thread'
|
||||
|
@ -453,9 +326,6 @@ class TestLogAndNotify(TestCase):
|
|||
send_mail_mock.call_args_list[1],
|
||||
review_url, 'you reviewed this add-on.')
|
||||
|
||||
self.addon = Addon.objects.get(pk=self.addon.pk)
|
||||
assert not self.addon.pending_info_request
|
||||
|
||||
@mock.patch('olympia.activity.utils.send_mail')
|
||||
def test_reviewer_reply(self, send_mail_mock):
|
||||
# One from the reviewer.
|
||||
|
@ -549,40 +419,6 @@ class TestLogAndNotify(TestCase):
|
|||
review_url,
|
||||
'you are member of the activity email cc group.')
|
||||
|
||||
@mock.patch('olympia.activity.utils.send_mail')
|
||||
def test_mail_needinfo_correct_subject(self, send_mail_mock):
|
||||
self.grant_permission(self.reviewer, 'None:None', ACTIVITY_MAIL_GROUP)
|
||||
action = amo.LOG.REQUEST_INFORMATION
|
||||
comments = u'Thïs is á reply'
|
||||
log_and_notify(action, comments, self.developer, self.version)
|
||||
|
||||
logs = ActivityLog.objects.filter(action=action.id)
|
||||
assert len(logs) == 1
|
||||
|
||||
recipients = self._recipients(send_mail_mock)
|
||||
sender = formataddr(
|
||||
(self.developer.name, NOTIFICATIONS_FROM_EMAIL))
|
||||
assert sender == send_mail_mock.call_args_list[0][1]['from_email']
|
||||
developer_subject = send_mail_mock.call_args_list[0][0][0]
|
||||
assert developer_subject == (
|
||||
u'Mozilla Add-ons: Action Required for '
|
||||
'%s %s' % (self.addon.name, self.version.version))
|
||||
reviewer_subject = send_mail_mock.call_args_list[1][0][0]
|
||||
assert reviewer_subject == u'Mozilla Add-ons: %s %s' % (
|
||||
self.addon.name, self.version.version)
|
||||
assert len(recipients) == 2
|
||||
# self.reviewers wasn't on the thread, but gets an email anyway.
|
||||
assert self.reviewer.email in recipients
|
||||
assert self.developer2.email in recipients
|
||||
review_url = absolutify(
|
||||
reverse('reviewers.review',
|
||||
kwargs={'addon_id': self.version.addon.pk,
|
||||
'channel': 'listed'},
|
||||
add_prefix=False))
|
||||
self._check_email(send_mail_mock.call_args_list[1],
|
||||
review_url,
|
||||
'you are member of the activity email cc group.')
|
||||
|
||||
@mock.patch('olympia.activity.utils.send_mail')
|
||||
def test_task_user_doesnt_get_mail(self, send_mail_mock):
|
||||
"""The task user account is used to auto-sign unlisted addons, amongst
|
||||
|
@ -717,7 +553,7 @@ class TestLogAndNotify(TestCase):
|
|||
@mock.patch('olympia.activity.utils.send_mail')
|
||||
def test_notify_about_previous_activity(self, send_mail_mock):
|
||||
# Create an activity to use when notifying.
|
||||
activity = self._create(amo.LOG.REQUEST_INFORMATION, self.reviewer)
|
||||
activity = self._create(amo.LOG.REVIEWER_REPLY_VERSION, self.reviewer)
|
||||
notify_about_activity_log(self.addon, self.version, activity)
|
||||
assert ActivityLog.objects.count() == 1 # No new activity created.
|
||||
|
||||
|
@ -732,16 +568,14 @@ class TestLogAndNotify(TestCase):
|
|||
# The reviewer who sent it doesn't get their email back.
|
||||
assert self.reviewer.email not in recipients
|
||||
|
||||
self._check_email_info_request(
|
||||
self._check_email(
|
||||
send_mail_mock.call_args_list[0],
|
||||
absolutify(self.addon.get_dev_url('versions')),
|
||||
'you are listed as an author of this add-on.',
|
||||
days_text=None)
|
||||
self._check_email_info_request(
|
||||
'you are listed as an author of this add-on.')
|
||||
self._check_email(
|
||||
send_mail_mock.call_args_list[1],
|
||||
absolutify(self.addon.get_dev_url('versions')),
|
||||
'you are listed as an author of this add-on.',
|
||||
days_text=None)
|
||||
'you are listed as an author of this add-on.')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
|
@ -148,7 +148,7 @@ class TestReviewNotesViewSetDetail(ReviewNotesViewSetDetailMixin, TestCase):
|
|||
self.user = user_factory()
|
||||
self.version = self.addon.find_latest_version(
|
||||
channel=amo.RELEASE_CHANNEL_LISTED)
|
||||
self.note = self.log(u'noôo!', amo.LOG.REQUEST_INFORMATION,
|
||||
self.note = self.log(u'noôo!', amo.LOG.REVIEWER_REPLY_VERSION,
|
||||
self.days_ago(0))
|
||||
self._set_tested_url()
|
||||
|
||||
|
@ -157,7 +157,7 @@ class TestReviewNotesViewSetDetail(ReviewNotesViewSetDetailMixin, TestCase):
|
|||
assert response.status_code == 200
|
||||
result = json.loads(response.content)
|
||||
assert result['id'] == self.note.pk
|
||||
assert result['action_label'] == amo.LOG.REQUEST_INFORMATION.short
|
||||
assert result['action_label'] == amo.LOG.REVIEWER_REPLY_VERSION.short
|
||||
assert result['comments'] == u'noôo!'
|
||||
assert result['highlight'] # Its the first reply so highlight
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import re
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from email.utils import formataddr
|
||||
from html import unescape
|
||||
|
||||
from django.conf import settings
|
||||
from django.forms import ValidationError
|
||||
from django.contrib.humanize.templatetags.humanize import apnumber
|
||||
from django.template import loader
|
||||
from django.utils import translation
|
||||
|
||||
|
@ -19,7 +18,6 @@ import olympia.core.logger
|
|||
from olympia import amo
|
||||
from olympia.access import acl
|
||||
from olympia.activity.models import ActivityLog, ActivityLogToken
|
||||
from olympia.addons.models import AddonReviewerFlags
|
||||
from olympia.amo.templatetags.jinja_helpers import absolutify
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.amo.utils import send_mail
|
||||
|
@ -220,14 +218,6 @@ def log_and_notify(action, comments, note_creator, version, perm_setting=None,
|
|||
|
||||
notify_about_activity_log(
|
||||
version.addon, version, note, perm_setting=perm_setting)
|
||||
|
||||
if action == amo.LOG.DEVELOPER_REPLY_VERSION:
|
||||
# When a developer repies by email, we automatically clear the
|
||||
# corresponding info request.
|
||||
AddonReviewerFlags.objects.update_or_create(
|
||||
addon=version.addon, defaults={'pending_info_request': None}
|
||||
)
|
||||
|
||||
return note
|
||||
|
||||
|
||||
|
@ -256,34 +246,10 @@ def notify_about_activity_log(addon, version, note, perm_setting=None,
|
|||
'url': absolutify(addon.get_dev_url('versions')),
|
||||
'SITE_URL': settings.SITE_URL,
|
||||
'email_reason': 'you are listed as an author of this add-on',
|
||||
'is_info_request': note.action == amo.LOG.REQUEST_INFORMATION.id,
|
||||
}
|
||||
|
||||
# Not being localised because we don't know the recipients locale.
|
||||
with translation.override('en-US'):
|
||||
if note.action == amo.LOG.REQUEST_INFORMATION.id:
|
||||
if addon.pending_info_request:
|
||||
days_left = (
|
||||
# We pad the time left with an extra hour so that the email
|
||||
# does not end up saying "6 days left" because a few
|
||||
# seconds or minutes passed between the datetime was saved
|
||||
# and the email was sent.
|
||||
addon.pending_info_request + timedelta(hours=1) -
|
||||
datetime.now()
|
||||
).days
|
||||
if days_left > 9:
|
||||
author_context_dict['number_of_days_left'] = (
|
||||
'%d days' % days_left)
|
||||
elif days_left > 1:
|
||||
author_context_dict['number_of_days_left'] = (
|
||||
'%s (%d) days' % (apnumber(days_left), days_left))
|
||||
else:
|
||||
author_context_dict['number_of_days_left'] = 'one (1) day'
|
||||
subject = u'Mozilla Add-ons: Action Required for %s %s' % (
|
||||
addon.name, version.version)
|
||||
reviewer_subject = u'Mozilla Add-ons: %s %s' % (
|
||||
addon.name, version.version)
|
||||
else:
|
||||
subject = reviewer_subject = u'Mozilla Add-ons: %s %s' % (
|
||||
addon.name, version.version)
|
||||
# Build and send the mail for authors.
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.14 on 2020-08-03 13:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('addons', '0017_addonreviewerflags_notified_about_expiring_delayed_rejections'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='addonreviewerflags',
|
||||
name='notified_about_expiring_info_request',
|
||||
field=models.NullBooleanField(default=None),
|
||||
),
|
||||
]
|
|
@ -1497,18 +1497,6 @@ class Addon(OnChangeMixin, ModelBase):
|
|||
except AddonReviewerFlags.DoesNotExist:
|
||||
return None
|
||||
|
||||
@property
|
||||
def pending_info_request(self):
|
||||
try:
|
||||
return self.reviewerflags.pending_info_request
|
||||
except AddonReviewerFlags.DoesNotExist:
|
||||
return None
|
||||
|
||||
@property
|
||||
def expired_info_request(self):
|
||||
info_request = self.pending_info_request
|
||||
return info_request and info_request < datetime.now()
|
||||
|
||||
@property
|
||||
def auto_approval_delayed_indefinitely(self):
|
||||
return self.auto_approval_delayed_until == datetime.max
|
||||
|
@ -1733,8 +1721,6 @@ class AddonReviewerFlags(ModelBase):
|
|||
default=None)
|
||||
auto_approval_delayed_until = models.DateTimeField(
|
||||
default=None, null=True)
|
||||
pending_info_request = models.DateTimeField(default=None, null=True)
|
||||
notified_about_expiring_info_request = models.BooleanField(default=False)
|
||||
notified_about_auto_approval_delay = models.NullBooleanField(default=None)
|
||||
notified_about_expiring_delayed_rejections = models.NullBooleanField(
|
||||
default=None)
|
||||
|
|
|
@ -1506,37 +1506,6 @@ class TestAddonModels(TestCase):
|
|||
flags.update(needs_admin_theme_review=True)
|
||||
assert addon.needs_admin_theme_review is True
|
||||
|
||||
def test_pending_info_request_property(self):
|
||||
addon = Addon.objects.get(pk=3615)
|
||||
# No flags: None
|
||||
assert addon.pending_info_request is None
|
||||
# Flag present, value is None (default): None.
|
||||
flags = AddonReviewerFlags.objects.create(addon=addon)
|
||||
assert flags.pending_info_request is None
|
||||
assert addon.pending_info_request is None
|
||||
# Flag present, value is a date.
|
||||
in_the_past = self.days_ago(1)
|
||||
flags.update(pending_info_request=in_the_past)
|
||||
assert addon.pending_info_request == in_the_past
|
||||
|
||||
def test_expired_info_request_property(self):
|
||||
addon = Addon.objects.get(pk=3615)
|
||||
# No flags: None
|
||||
assert addon.expired_info_request is None
|
||||
# Flag present, value is None (default): None.
|
||||
flags = AddonReviewerFlags.objects.create(addon=addon)
|
||||
assert flags.pending_info_request is None
|
||||
assert addon.expired_info_request is None
|
||||
# Flag present, value is a date in the past.
|
||||
in_the_past = self.days_ago(1)
|
||||
flags.update(pending_info_request=in_the_past)
|
||||
assert addon.expired_info_request
|
||||
|
||||
# Flag present, value is a date in the future.
|
||||
in_the_future = datetime.now() + timedelta(days=2)
|
||||
flags.update(pending_info_request=in_the_future)
|
||||
assert not addon.expired_info_request
|
||||
|
||||
def test_reset_notified_about_auto_approval_delay(self):
|
||||
addon = Addon.objects.get(pk=3615)
|
||||
assert not AddonReviewerFlags.objects.filter(addon=addon).exists()
|
||||
|
|
|
@ -186,6 +186,7 @@ class REQUEST_VERSION(_LOG):
|
|||
review_queue = True
|
||||
|
||||
|
||||
# Obsolete now that we have pending rejections, kept for compatibility.
|
||||
class REQUEST_INFORMATION(_LOG):
|
||||
id = 44
|
||||
format = _(u'{addon} {version} more information requested.')
|
||||
|
|
|
@ -25,10 +25,9 @@ from rest_framework.exceptions import Throttled
|
|||
from olympia import amo
|
||||
from olympia.access import acl
|
||||
from olympia.activity.models import ActivityLog
|
||||
from olympia.activity.utils import log_and_notify
|
||||
from olympia.addons import tasks as addons_tasks
|
||||
from olympia.addons.models import (
|
||||
Addon, AddonApprovalsCounter, AddonCategory, AddonReviewerFlags, AddonUser,
|
||||
Addon, AddonApprovalsCounter, AddonCategory, AddonUser,
|
||||
AddonUserPendingConfirmation, Category, DeniedSlug, Preview)
|
||||
from olympia.addons.utils import verify_mozilla_trademark
|
||||
from olympia.amo.fields import HttpHttpsOnlyURLField, ReCaptchaField
|
||||
|
@ -788,39 +787,10 @@ class VersionForm(WithSourceMixin, forms.ModelForm):
|
|||
approval_notes = forms.CharField(
|
||||
widget=TranslationTextarea(attrs={'rows': 4}), required=False)
|
||||
source = forms.FileField(required=False, widget=SourceFileInput)
|
||||
clear_pending_info_request = forms.BooleanField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = Version
|
||||
fields = ('release_notes', 'clear_pending_info_request',
|
||||
'approval_notes', 'source',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.request = kwargs.pop('request')
|
||||
super(VersionForm, self).__init__(*args, **kwargs)
|
||||
# Fetch latest reviewer comment if the addon has a pending info
|
||||
# request, so that the template in which the form is used can display
|
||||
# that comment.
|
||||
if self.instance and self.instance.addon.pending_info_request:
|
||||
try:
|
||||
self.pending_info_request_comment = (
|
||||
ActivityLog.objects.for_addons(self.instance.addon)
|
||||
.filter(action=amo.LOG.REQUEST_INFORMATION.id)
|
||||
.latest('pk')).details['comments']
|
||||
except (ActivityLog.DoesNotExist, KeyError):
|
||||
self.pending_info_request_comment = ''
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super(VersionForm, self).save(*args, **kwargs)
|
||||
# Clear pending info request on the addon if requested, adding an entry
|
||||
# in the Activity Log to indicate that.
|
||||
if self.cleaned_data.get('clear_pending_info_request'):
|
||||
AddonReviewerFlags.objects.update_or_create(
|
||||
addon=self.instance.addon,
|
||||
defaults={'pending_info_request': None})
|
||||
log_and_notify(
|
||||
amo.LOG.DEVELOPER_CLEAR_INFO_REQUEST, None,
|
||||
self.request.user, self.instance)
|
||||
fields = ('release_notes', 'approval_notes', 'source',)
|
||||
|
||||
|
||||
class AppVersionChoiceField(forms.ModelChoiceField):
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
{{ reviewer_form.approval_notes }}
|
||||
<p>{{ _('These notes will only be visible to you and our reviewers.') }}</p>
|
||||
</div>
|
||||
{% include "devhub/includes/clear_pending_info_request.html" %}
|
||||
<div class="submission-buttons addon-submission-field">
|
||||
<button type="submit">
|
||||
{{ _('Submit Version') }}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{% if addon.pending_info_request %}
|
||||
<div class="addon-submission-field clear-info-request">
|
||||
<strong>{{ _('Pending information request:') }}</strong>
|
||||
<blockquote><p> {{ reviewer_form.pending_info_request_comment }} </p></blockquote>
|
||||
{{ reviewer_form.clear_pending_info_request }} <label for="{{ reviewer_form.clear_pending_info_request.auto_id }}">
|
||||
{{ _('This request for information has been resolved') }}</label>
|
||||
</div>
|
||||
{% endif %}
|
|
@ -113,9 +113,6 @@
|
|||
<td>
|
||||
{{ field.errors }}
|
||||
{{ field }}
|
||||
{% with reviewer_form = version_form %}
|
||||
{% include "devhub/includes/clear_pending_info_request.html" %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
{% endwith %}
|
||||
</tr>
|
||||
|
|
|
@ -128,28 +128,24 @@ class TestDevFilesStatus(TestCase):
|
|||
|
||||
@pytest.mark.parametrize(
|
||||
'action1,action2,action3,expected_count', (
|
||||
(LOG.REQUEST_INFORMATION, LOG.REQUEST_INFORMATION,
|
||||
LOG.REQUEST_INFORMATION, 3),
|
||||
# Tests with Developer_Reply
|
||||
(LOG.DEVELOPER_REPLY_VERSION, LOG.REQUEST_INFORMATION,
|
||||
LOG.REQUEST_INFORMATION, 2),
|
||||
(LOG.REQUEST_INFORMATION, LOG.DEVELOPER_REPLY_VERSION,
|
||||
LOG.REQUEST_INFORMATION, 1),
|
||||
(LOG.REQUEST_INFORMATION, LOG.REQUEST_INFORMATION,
|
||||
(LOG.REVIEWER_REPLY_VERSION, LOG.DEVELOPER_REPLY_VERSION,
|
||||
LOG.REVIEWER_REPLY_VERSION, 1),
|
||||
(LOG.REVIEWER_REPLY_VERSION, LOG.REVIEWER_REPLY_VERSION,
|
||||
LOG.DEVELOPER_REPLY_VERSION, 0),
|
||||
# Tests with Approval
|
||||
(LOG.APPROVE_VERSION, LOG.REQUEST_INFORMATION,
|
||||
LOG.REQUEST_INFORMATION, 2),
|
||||
(LOG.REQUEST_INFORMATION, LOG.APPROVE_VERSION,
|
||||
LOG.REQUEST_INFORMATION, 1),
|
||||
(LOG.REQUEST_INFORMATION, LOG.REQUEST_INFORMATION,
|
||||
(LOG.APPROVE_VERSION, LOG.REVIEWER_REPLY_VERSION,
|
||||
LOG.REVIEWER_REPLY_VERSION, 2),
|
||||
(LOG.REVIEWER_REPLY_VERSION, LOG.APPROVE_VERSION,
|
||||
LOG.REVIEWER_REPLY_VERSION, 1),
|
||||
(LOG.REVIEWER_REPLY_VERSION, LOG.REVIEWER_REPLY_VERSION,
|
||||
LOG.APPROVE_VERSION, 0),
|
||||
# Tests with Rejection
|
||||
(LOG.REJECT_VERSION, LOG.REQUEST_INFORMATION,
|
||||
LOG.REQUEST_INFORMATION, 2),
|
||||
(LOG.REQUEST_INFORMATION, LOG.REJECT_VERSION,
|
||||
LOG.REQUEST_INFORMATION, 1),
|
||||
(LOG.REQUEST_INFORMATION, LOG.REQUEST_INFORMATION,
|
||||
(LOG.REJECT_VERSION, LOG.REVIEWER_REPLY_VERSION,
|
||||
LOG.REVIEWER_REPLY_VERSION, 2),
|
||||
(LOG.REVIEWER_REPLY_VERSION, LOG.REJECT_VERSION,
|
||||
LOG.REVIEWER_REPLY_VERSION, 1),
|
||||
(LOG.REVIEWER_REPLY_VERSION, LOG.REVIEWER_REPLY_VERSION,
|
||||
LOG.REJECT_VERSION, 0),
|
||||
)
|
||||
)
|
||||
|
|
|
@ -23,7 +23,7 @@ from waffle.testutils import override_switch
|
|||
from olympia import amo
|
||||
from olympia.activity.models import ActivityLog
|
||||
from olympia.addons.models import (
|
||||
Addon, AddonCategory, AddonReviewerFlags, Category)
|
||||
Addon, AddonCategory, Category)
|
||||
from olympia.amo.tests import (
|
||||
TestCase, addon_factory, create_default_webext_appversion, formset,
|
||||
initial, user_factory, version_factory)
|
||||
|
@ -2361,60 +2361,6 @@ class TestVersionSubmitDetails(TestSubmitBase):
|
|||
response, reverse('devhub.submit.version.finish',
|
||||
args=[self.addon.slug, self.version.pk]))
|
||||
|
||||
def test_show_request_for_information(self):
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addon, pending_info_request=self.days_ago(2))
|
||||
ActivityLog.create(
|
||||
amo.LOG.REVIEWER_REPLY_VERSION, self.addon, self.version,
|
||||
user=self.user, details={'comments': 'this should not be shown'})
|
||||
ActivityLog.create(
|
||||
amo.LOG.REQUEST_INFORMATION, self.addon, self.version,
|
||||
user=self.user, details={'comments': 'this is an info request'})
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
assert b'this should not be shown' not in response.content
|
||||
assert b'this is an info request' in response.content
|
||||
|
||||
def test_dont_show_request_for_information_if_none_pending(self):
|
||||
ActivityLog.create(
|
||||
amo.LOG.REVIEWER_REPLY_VERSION, self.addon, self.version,
|
||||
user=self.user, details={'comments': 'this should not be shown'})
|
||||
ActivityLog.create(
|
||||
amo.LOG.REQUEST_INFORMATION, self.addon, self.version,
|
||||
user=self.user, details={'comments': 'this is an info request'})
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
assert b'this should not be shown' not in response.content
|
||||
assert b'this is an info request' not in response.content
|
||||
|
||||
def test_clear_request_for_information(self):
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addon, pending_info_request=self.days_ago(2))
|
||||
response = self.client.post(
|
||||
self.url, {'clear_pending_info_request': True})
|
||||
self.assert3xx(
|
||||
response, reverse('devhub.submit.version.finish',
|
||||
args=[self.addon.slug, self.version.pk]))
|
||||
flags = AddonReviewerFlags.objects.get(addon=self.addon)
|
||||
assert flags.pending_info_request is None
|
||||
activity = ActivityLog.objects.for_addons(self.addon).filter(
|
||||
action=amo.LOG.DEVELOPER_CLEAR_INFO_REQUEST.id).get()
|
||||
assert activity.user == self.user
|
||||
assert activity.arguments == [self.addon, self.version]
|
||||
|
||||
def test_dont_clear_request_for_information(self):
|
||||
past_date = self.days_ago(2)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addon, pending_info_request=past_date)
|
||||
response = self.client.post(self.url)
|
||||
self.assert3xx(
|
||||
response, reverse('devhub.submit.version.finish',
|
||||
args=[self.addon.slug, self.version.pk]))
|
||||
flags = AddonReviewerFlags.objects.get(addon=self.addon)
|
||||
assert flags.pending_info_request == past_date
|
||||
assert not ActivityLog.objects.for_addons(self.addon).filter(
|
||||
action=amo.LOG.DEVELOPER_CLEAR_INFO_REQUEST.id).exists()
|
||||
|
||||
def test_can_cancel_review(self):
|
||||
addon = self.get_addon()
|
||||
addon_status = addon.status
|
||||
|
|
|
@ -606,9 +606,9 @@ class TestVersion(TestCase):
|
|||
def test_pending_activity_count(self):
|
||||
v2, _ = self._extra_version_and_file(amo.STATUS_AWAITING_REVIEW)
|
||||
# Add some activity log messages
|
||||
ActivityLog.create(amo.LOG.REQUEST_INFORMATION, v2.addon, v2,
|
||||
ActivityLog.create(amo.LOG.REVIEWER_REPLY_VERSION, v2.addon, v2,
|
||||
user=self.user)
|
||||
ActivityLog.create(amo.LOG.REQUEST_INFORMATION, v2.addon, v2,
|
||||
ActivityLog.create(amo.LOG.REVIEWER_REPLY_VERSION, v2.addon, v2,
|
||||
user=self.user)
|
||||
|
||||
response = self.client.get(self.url)
|
||||
|
@ -830,52 +830,6 @@ class TestVersionEditDetails(TestVersionEditBase):
|
|||
assert version.source
|
||||
assert not version.addon.needs_admin_code_review
|
||||
|
||||
def test_show_request_for_information(self):
|
||||
self.user = UserProfile.objects.latest('pk')
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addon, pending_info_request=self.days_ago(2))
|
||||
ActivityLog.create(
|
||||
amo.LOG.REVIEWER_REPLY_VERSION, self.addon, self.version,
|
||||
user=self.user, details={'comments': 'this should not be shown'})
|
||||
ActivityLog.create(
|
||||
amo.LOG.REQUEST_INFORMATION, self.addon, self.version,
|
||||
user=self.user, details={'comments': 'this is an info request'})
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
assert b'this should not be shown' not in response.content
|
||||
assert b'this is an info request' in response.content
|
||||
|
||||
def test_dont_show_request_for_information_if_none_pending(self):
|
||||
self.user = UserProfile.objects.latest('pk')
|
||||
ActivityLog.create(
|
||||
amo.LOG.REVIEWER_REPLY_VERSION, self.addon, self.version,
|
||||
user=self.user, details={'comments': 'this should not be shown'})
|
||||
ActivityLog.create(
|
||||
amo.LOG.REQUEST_INFORMATION, self.addon, self.version,
|
||||
user=self.user, details={'comments': 'this is an info request'})
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
assert b'this should not be shown' not in response.content
|
||||
assert b'this is an info request' not in response.content
|
||||
|
||||
def test_clear_request_for_information(self):
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addon, pending_info_request=self.days_ago(2))
|
||||
response = self.client.post(
|
||||
self.url, self.formset(clear_pending_info_request=True))
|
||||
assert response.status_code == 302
|
||||
flags = AddonReviewerFlags.objects.get(addon=self.addon)
|
||||
assert flags.pending_info_request is None
|
||||
|
||||
def test_dont_clear_request_for_information(self):
|
||||
past_date = self.days_ago(2)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addon, pending_info_request=past_date)
|
||||
response = self.client.post(self.url, self.formset())
|
||||
assert response.status_code == 302
|
||||
flags = AddonReviewerFlags.objects.get(addon=self.addon)
|
||||
assert flags.pending_info_request == past_date
|
||||
|
||||
|
||||
class TestVersionEditSearchEngine(TestVersionEditMixin, TestCase):
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=605941
|
||||
|
|
|
@ -29,7 +29,6 @@ from olympia.access import acl
|
|||
from olympia.accounts.utils import redirect_for_login, _is_safe_url
|
||||
from olympia.accounts.views import API_TOKEN_COOKIE, logout_user
|
||||
from olympia.activity.models import ActivityLog, VersionLog
|
||||
from olympia.activity.utils import log_and_notify
|
||||
from olympia.addons.models import (
|
||||
Addon, AddonReviewerFlags, AddonUser, AddonUserPendingConfirmation)
|
||||
from olympia.addons.views import BaseFilter
|
||||
|
@ -1004,7 +1003,6 @@ def version_edit(request, addon_id, addon, version_id):
|
|||
request.POST or None,
|
||||
request.FILES or None,
|
||||
instance=version,
|
||||
request=request,
|
||||
) if not static_theme else None
|
||||
|
||||
data = {}
|
||||
|
@ -1038,17 +1036,9 @@ def version_edit(request, addon_id, addon, version_id):
|
|||
_log_max_version_change(addon, version, form.instance)
|
||||
|
||||
if 'version_form' in data:
|
||||
# VersionForm.save() clear the pending info request if the
|
||||
# developer specifically asked for it, but we've got additional
|
||||
# things to do here that depend on it.
|
||||
had_pending_info_request = bool(addon.pending_info_request)
|
||||
data['version_form'].save()
|
||||
|
||||
if 'approval_notes' in version_form.changed_data:
|
||||
if had_pending_info_request:
|
||||
log_and_notify(amo.LOG.APPROVAL_NOTES_CHANGED, None,
|
||||
request.user, version)
|
||||
else:
|
||||
ActivityLog.create(amo.LOG.APPROVAL_NOTES_CHANGED,
|
||||
addon, version, request.user)
|
||||
|
||||
|
@ -1057,10 +1047,6 @@ def version_edit(request, addon_id, addon, version_id):
|
|||
AddonReviewerFlags.objects.update_or_create(
|
||||
addon=addon, defaults={'needs_admin_code_review': True})
|
||||
|
||||
if had_pending_info_request:
|
||||
log_and_notify(amo.LOG.SOURCE_CODE_UPLOADED, None,
|
||||
request.user, version)
|
||||
else:
|
||||
ActivityLog.create(amo.LOG.SOURCE_CODE_UPLOADED,
|
||||
addon, version, request.user)
|
||||
|
||||
|
@ -1522,7 +1508,7 @@ def _submit_details(request, addon, version):
|
|||
if not static_theme:
|
||||
# Static themes don't need this form
|
||||
reviewer_form = forms.VersionForm(
|
||||
post_data, instance=latest_version, request=request)
|
||||
post_data, instance=latest_version)
|
||||
context.update(reviewer_form=reviewer_form)
|
||||
forms_list.append(reviewer_form)
|
||||
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
import olympia.core.logger
|
||||
|
||||
from olympia import amo
|
||||
from olympia.activity.models import ActivityLog
|
||||
from olympia.activity.utils import notify_about_activity_log
|
||||
from olympia.addons.models import Addon
|
||||
from olympia.users.notifications import reviewer_reviewed
|
||||
|
||||
|
||||
log = olympia.core.logger.getLogger('z.reviewers.send_info_request')
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Notify developers with pending info requests about to expire'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# Fetch addons with request for information expiring in one day.
|
||||
one_day_in_the_future = datetime.now() + timedelta(days=1)
|
||||
qs = Addon.objects.filter(
|
||||
reviewerflags__notified_about_expiring_info_request=False,
|
||||
reviewerflags__pending_info_request__lt=one_day_in_the_future)
|
||||
for addon in qs:
|
||||
# The note we need to send the mail should always going to be the
|
||||
# last information request, as making a new one extends the
|
||||
# deadline.
|
||||
note = ActivityLog.objects.for_addons(addon).filter(
|
||||
action=amo.LOG.REQUEST_INFORMATION.id).latest('pk')
|
||||
version = note.versionlog_set.latest('pk').version
|
||||
log.info(
|
||||
'Notifying developers of %s about expiring info request',
|
||||
addon.pk)
|
||||
# This re-sends the notification sent when the information was
|
||||
# requested, but with the new delay in the body of the email now
|
||||
# that the notification is about to expire.
|
||||
notify_about_activity_log(
|
||||
addon, version, note, perm_setting=reviewer_reviewed.short,
|
||||
send_to_reviewers=False, send_to_staff=False)
|
||||
addon.reviewerflags.update(
|
||||
notified_about_expiring_info_request=True)
|
|
@ -44,8 +44,6 @@ VIEW_QUEUE_FLAGS = (
|
|||
('needs_admin_theme_review', 'needs-admin-theme-review',
|
||||
_('Needs Admin Static Theme Review')),
|
||||
('is_restart_required', 'is_restart_required', _('Requires Restart')),
|
||||
('pending_info_request', 'info', _('More Information Requested')),
|
||||
('expired_info_request', 'expired-info', _('Expired Information Request')),
|
||||
('sources_provided', 'sources-provided', _('Sources provided')),
|
||||
('is_webextension', 'webextension', _('WebExtension')),
|
||||
('auto_approval_delayed_temporarily', 'auto-approval-delayed-temporarily',
|
||||
|
@ -124,8 +122,6 @@ class ViewQueue(RawSQLModel):
|
|||
source = models.CharField(max_length=100)
|
||||
is_webextension = models.BooleanField()
|
||||
latest_version = models.CharField(max_length=255)
|
||||
pending_info_request = models.DateTimeField()
|
||||
expired_info_request = models.NullBooleanField()
|
||||
auto_approval_delayed_temporarily = models.NullBooleanField()
|
||||
auto_approval_delayed_indefinitely = models.NullBooleanField()
|
||||
waiting_time_days = models.IntegerField()
|
||||
|
@ -149,11 +145,6 @@ class ViewQueue(RawSQLModel):
|
|||
('needs_admin_theme_review',
|
||||
'addons_addonreviewerflags.needs_admin_theme_review'),
|
||||
('latest_version', 'versions.version'),
|
||||
('pending_info_request',
|
||||
'addons_addonreviewerflags.pending_info_request'),
|
||||
('expired_info_request', (
|
||||
'TIMEDIFF(addons_addonreviewerflags.pending_info_request,'
|
||||
'NOW()) < 0')),
|
||||
('auto_approval_delayed_temporarily', (
|
||||
'TIMEDIFF(addons_addonreviewerflags.'
|
||||
'auto_approval_delayed_until, NOW()) > 0 AND '
|
||||
|
|
|
@ -45,7 +45,6 @@ class AddonReviewerFlagsSerializer(serializers.ModelSerializer):
|
|||
'needs_admin_code_review',
|
||||
'needs_admin_content_review',
|
||||
'needs_admin_theme_review',
|
||||
'pending_info_request'
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
{{ _('Auto-approved Add-ons – Add-ons for Firefox') }}
|
||||
{% elif tab == "moderated" %}
|
||||
{{ _('Review Moderation – Add-ons for Firefox') }}
|
||||
{% elif tab == "expired_info_requests" %}
|
||||
{{ _('Expired Information Requests – Add-ons for Firefox') }}
|
||||
{% elif tab == "unlisted_queue_all" %}
|
||||
{{ _('Unlisted Add-ons – Add-ons for Firefox') }}
|
||||
{% elif tab == "pending_rejection" %}
|
||||
|
|
|
@ -310,15 +310,6 @@
|
|||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if addon.pending_info_request %}
|
||||
<li>
|
||||
<button data-api-url="{{ drf_url('reviewers-addon-flags', addon.pk) }}"
|
||||
data-api-method="patch"
|
||||
data-api-data="{"pending_info_request": null}"
|
||||
id="clear_pending_info_request" class="oneoff" type="button">{{ _('Clear information request') }}</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if has_versions_pending_rejection %}
|
||||
<li>
|
||||
<button data-api-url="{{ drf_url('reviewers-addon-clear-pending-rejections', addon.pk) }}"
|
||||
|
|
|
@ -132,13 +132,6 @@ def queue_tabnav(context):
|
|||
)
|
||||
|
||||
if acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN):
|
||||
tabnav.append(
|
||||
('expired_info_requests', 'queue_expired_info_requests',
|
||||
(ungettext('Expired Info Request ({0})',
|
||||
'Expired Info Requests ({0})',
|
||||
counts['expired_info_requests'])
|
||||
.format(counts['expired_info_requests']))),
|
||||
)
|
||||
tabnav.append(
|
||||
('pending_rejection', 'queue_pending_rejection',
|
||||
(ungettext('Pending Rejection ({0})',
|
||||
|
|
|
@ -9,9 +9,7 @@ from django.core.management import call_command
|
|||
from django.test.testcases import TransactionTestCase
|
||||
from olympia import amo
|
||||
from olympia.activity.models import ActivityLog
|
||||
from olympia.activity.utils import ACTIVITY_MAIL_GROUP
|
||||
from olympia.addons.models import (
|
||||
AddonApprovalsCounter, AddonReviewerFlags, AddonUser)
|
||||
from olympia.addons.models import AddonApprovalsCounter, AddonReviewerFlags
|
||||
from olympia.amo.tests import (
|
||||
TestCase, addon_factory, file_factory, user_factory, version_factory)
|
||||
from olympia.amo.utils import days_ago
|
||||
|
@ -673,68 +671,6 @@ class TestRecalculatePostReviewWeightsCommand(TestCase):
|
|||
assert summary.modified != old_modified_date
|
||||
|
||||
|
||||
class TestSendInfoRequestLastWarningNotification(TestCase):
|
||||
@mock.patch('olympia.reviewers.management.commands.'
|
||||
'send_info_request_last_warning_notifications.'
|
||||
'notify_about_activity_log')
|
||||
def test_non_expired(self, notify_about_activity_log_mock):
|
||||
addon_factory() # Normal add-on, no pending info request.
|
||||
addon_not_expired = addon_factory()
|
||||
flags = AddonReviewerFlags.objects.create(
|
||||
addon=addon_not_expired,
|
||||
pending_info_request=datetime.now() + timedelta(days=1, hours=3))
|
||||
call_command('send_info_request_last_warning_notifications')
|
||||
assert notify_about_activity_log_mock.call_count == 0
|
||||
assert flags.notified_about_expiring_info_request is False
|
||||
|
||||
@mock.patch('olympia.reviewers.management.commands.'
|
||||
'send_info_request_last_warning_notifications.'
|
||||
'notify_about_activity_log')
|
||||
def test_already_notified(self, notify_about_activity_log_mock):
|
||||
addon_factory()
|
||||
addon_already_notified = addon_factory()
|
||||
flags = AddonReviewerFlags.objects.create(
|
||||
addon=addon_already_notified,
|
||||
pending_info_request=datetime.now() + timedelta(hours=23),
|
||||
notified_about_expiring_info_request=True)
|
||||
call_command('send_info_request_last_warning_notifications')
|
||||
assert notify_about_activity_log_mock.call_count == 0
|
||||
assert flags.notified_about_expiring_info_request is True
|
||||
|
||||
def test_normal(self):
|
||||
addon = addon_factory()
|
||||
author = user_factory(username=u'Authør')
|
||||
AddonUser.objects.create(addon=addon, user=author)
|
||||
# Add a pending info request expiring soon.
|
||||
flags = AddonReviewerFlags.objects.create(
|
||||
addon=addon,
|
||||
pending_info_request=datetime.now() + timedelta(hours=23),
|
||||
notified_about_expiring_info_request=False)
|
||||
# Create reviewer and staff users, and create the request for info
|
||||
# activity. Neither the reviewer nor the staff user should be cc'ed.
|
||||
reviewer = user_factory(username=u'Revièwer')
|
||||
self.grant_permission(reviewer, 'Addons:Review')
|
||||
ActivityLog.create(
|
||||
amo.LOG.REQUEST_INFORMATION, addon, addon.current_version,
|
||||
user=reviewer, details={'comments': u'Fly you fôöls!'})
|
||||
staff = user_factory(username=u'Staff Ûser')
|
||||
self.grant_permission(staff, 'Some:Perm', name=ACTIVITY_MAIL_GROUP)
|
||||
|
||||
# Fire the command.
|
||||
call_command('send_info_request_last_warning_notifications')
|
||||
|
||||
assert len(mail.outbox) == 1
|
||||
msg = mail.outbox[0]
|
||||
assert msg.to == [author.email]
|
||||
assert msg.subject == u'Mozilla Add-ons: Action Required for %s %s' % (
|
||||
addon.name, addon.current_version.version)
|
||||
assert 'an issue when reviewing ' in msg.body
|
||||
assert 'within one (1) day' in msg.body
|
||||
|
||||
flags.reload()
|
||||
assert flags.notified_about_expiring_info_request is True
|
||||
|
||||
|
||||
class TestSendPendingRejectionLastWarningNotification(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
|
|
@ -161,13 +161,6 @@ class TestExtensionQueueWithAwaitingReview(TestQueue):
|
|||
assert queue.flags == [
|
||||
('needs-admin-code-review', 'Needs Admin Code Review')]
|
||||
|
||||
def test_flags_info_request(self):
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.new_addon(),
|
||||
pending_info_request=datetime.now() + timedelta(days=6))
|
||||
queue = self.Queue.objects.get()
|
||||
assert queue.flags == [('info', 'More Information Requested')]
|
||||
|
||||
def test_flags_is_restart_required(self):
|
||||
self.new_addon().find_latest_version(self.channel).all_files[0].update(
|
||||
is_restart_required=True)
|
||||
|
|
|
@ -411,13 +411,6 @@ class TestReviewLog(ReviewerTest):
|
|||
assert pq(response.content)('#log-listing tr td').eq(1).text() == (
|
||||
'Add-on has been deleted.')
|
||||
|
||||
def test_request_info_logs(self):
|
||||
self.make_an_approval(amo.LOG.REQUEST_INFORMATION)
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
assert pq(response.content)('#log-listing tr td a').eq(1).text() == (
|
||||
'More information requested')
|
||||
|
||||
def test_super_review_logs(self):
|
||||
self.make_an_approval(amo.LOG.REQUEST_ADMIN_REVIEW_CODE)
|
||||
response = self.client.get(self.url)
|
||||
|
@ -610,53 +603,35 @@ class TestDashboard(TestCase):
|
|||
admins_group = Group.objects.create(name='Admins', rules='*:*')
|
||||
GroupUser.objects.create(user=self.user, group=admins_group)
|
||||
|
||||
# Pending addon with expired info request.
|
||||
addon1 = addon_factory(name=u'Pending Addön 1',
|
||||
status=amo.STATUS_NOMINATED)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon1,
|
||||
pending_info_request=self.days_ago(2))
|
||||
# Pending addon
|
||||
addon_factory(name='Pending Addön', status=amo.STATUS_NOMINATED)
|
||||
|
||||
# Public addon with expired info request.
|
||||
addon2 = addon_factory(name=u'Public Addön 2',
|
||||
status=amo.STATUS_APPROVED)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon2,
|
||||
pending_info_request=self.days_ago(42))
|
||||
# Public addon
|
||||
addon = addon_factory(name='Public Addön', status=amo.STATUS_APPROVED)
|
||||
|
||||
# Deleted addon with expired info request.
|
||||
addon3 = addon_factory(name=u'Deleted Addön 3',
|
||||
status=amo.STATUS_DELETED)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon3,
|
||||
pending_info_request=self.days_ago(42))
|
||||
# Deleted addon
|
||||
addon_factory(name='Deleted Addön', status=amo.STATUS_DELETED)
|
||||
|
||||
# Mozilla-disabled addon with expired info request.
|
||||
addon4 = addon_factory(name=u'Disabled Addön 4',
|
||||
status=amo.STATUS_DISABLED)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon4,
|
||||
pending_info_request=self.days_ago(42))
|
||||
# Mozilla-disabled addon
|
||||
addon_factory(name='Disabled Addön', status=amo.STATUS_DISABLED)
|
||||
|
||||
# Incomplete addon with expired info request.
|
||||
addon5 = addon_factory(name=u'Incomplete Addön 5',
|
||||
status=amo.STATUS_NULL)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon5,
|
||||
pending_info_request=self.days_ago(42))
|
||||
# Incomplete addon
|
||||
addon_factory(name='Incomplete Addön', status=amo.STATUS_NULL)
|
||||
|
||||
# Invisible (user-disabled) addon with expired info request.
|
||||
addon6 = addon_factory(name=u'Incomplete Addön 5',
|
||||
status=amo.STATUS_APPROVED,
|
||||
# Invisible (user-disabled) addon
|
||||
addon_factory(name='Invisible Addön', status=amo.STATUS_APPROVED,
|
||||
disabled_by_user=True)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon6,
|
||||
pending_info_request=self.days_ago(42))
|
||||
|
||||
pending_rejection = addon_factory(name='Pending Rejection Addôn')
|
||||
VersionReviewerFlags.objects.create(
|
||||
version=pending_rejection.current_version,
|
||||
pending_rejection=datetime.now() + timedelta(days=4)
|
||||
)
|
||||
|
||||
# Rating
|
||||
rating = Rating.objects.create(
|
||||
addon=addon1, version=addon1.current_version, user=self.user,
|
||||
flag=True, body=u'This âdd-on sucks!!111', rating=1,
|
||||
addon=addon, version=addon.current_version, user=self.user,
|
||||
flag=True, body='This âdd-on sucks!!111', rating=1,
|
||||
editorreview=True)
|
||||
rating.ratingflag_set.create()
|
||||
|
||||
|
@ -689,7 +664,6 @@ class TestDashboard(TestCase):
|
|||
reverse('reviewers.unlisted_queue_all'),
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide',
|
||||
reverse('reviewers.motd'),
|
||||
reverse('reviewers.queue_expired_info_requests'),
|
||||
reverse('reviewers.queue_pending_rejection'),
|
||||
]
|
||||
links = [link.attrib['href'] for link in doc('.dashboard a')]
|
||||
|
@ -709,7 +683,7 @@ class TestDashboard(TestCase):
|
|||
'Ratings Awaiting Moderation (1)')
|
||||
# admin tools
|
||||
assert (doc('.dashboard a')[24].text ==
|
||||
'Expired Information Requests (2)')
|
||||
'Add-ons Pending Rejection (1)')
|
||||
|
||||
def test_can_see_all_through_reviewer_view_all_permission(self):
|
||||
self.grant_permission(self.user, 'ReviewerTools:View')
|
||||
|
@ -741,7 +715,6 @@ class TestDashboard(TestCase):
|
|||
reverse('reviewers.unlisted_queue_all'),
|
||||
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide',
|
||||
reverse('reviewers.motd'),
|
||||
reverse('reviewers.queue_expired_info_requests'),
|
||||
reverse('reviewers.queue_pending_rejection'),
|
||||
]
|
||||
links = [link.attrib['href'] for link in doc('.dashboard a')]
|
||||
|
@ -1410,7 +1383,6 @@ class TestQueueBasics(QueueTest):
|
|||
doc = pq(response.content)
|
||||
links = doc('.tabnav li a').map(lambda i, e: e.attrib['href'])
|
||||
expected.extend([
|
||||
reverse('reviewers.queue_expired_info_requests'),
|
||||
reverse('reviewers.queue_pending_rejection'),
|
||||
])
|
||||
assert links == expected
|
||||
|
@ -2194,11 +2166,11 @@ class TestAutoApprovedQueue(QueueTest):
|
|||
def test_results(self):
|
||||
self.login_with_permission()
|
||||
self.generate_files()
|
||||
with self.assertNumQueries(27):
|
||||
# 27 queries is a lot, but it used to be much much worse.
|
||||
with self.assertNumQueries(26):
|
||||
# 26 queries is a lot, but it used to be much much worse.
|
||||
# - 2 for savepoints because we're in tests
|
||||
# - 2 for user/groups
|
||||
# - 12 for various queue counts, including current one
|
||||
# - 11 for various queue counts, including current one
|
||||
# (unfortunately duplicated because it appears in two
|
||||
# completely different places)
|
||||
# - 3 for the addons in the queues and their files (regardless of
|
||||
|
@ -2263,84 +2235,6 @@ class TestAutoApprovedQueue(QueueTest):
|
|||
self._test_results()
|
||||
|
||||
|
||||
class TestExpiredInfoRequestsQueue(QueueTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestExpiredInfoRequestsQueue, self).setUp()
|
||||
self.url = reverse('reviewers.queue_expired_info_requests')
|
||||
|
||||
def generate_files(self):
|
||||
# Extra add-on with no pending info request.
|
||||
addon_factory(name=u'Extra Addôn 1')
|
||||
|
||||
# Extra add-on with a non-expired pending info request.
|
||||
extra_addon = addon_factory(name=u'Extra Addôn 2')
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=extra_addon,
|
||||
pending_info_request=datetime.now() + timedelta(days=1))
|
||||
|
||||
# Pending addon with expired info request.
|
||||
addon1 = addon_factory(name=u'Pending Addön 1',
|
||||
status=amo.STATUS_NOMINATED)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon1,
|
||||
pending_info_request=self.days_ago(2))
|
||||
|
||||
# Public addon with expired info request.
|
||||
addon2 = addon_factory(name=u'Public Addön 2',
|
||||
status=amo.STATUS_APPROVED)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon2,
|
||||
pending_info_request=self.days_ago(42))
|
||||
|
||||
# Deleted addon with expired info request.
|
||||
addon3 = addon_factory(name=u'Deleted Addön 3',
|
||||
status=amo.STATUS_DELETED)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon3,
|
||||
pending_info_request=self.days_ago(42))
|
||||
|
||||
# Mozilla-disabled addon with expired info request.
|
||||
addon4 = addon_factory(name=u'Disabled Addön 4',
|
||||
status=amo.STATUS_DISABLED)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon4,
|
||||
pending_info_request=self.days_ago(42))
|
||||
|
||||
# Incomplete addon with expired info request.
|
||||
addon5 = addon_factory(name=u'Incomplete Addön 5',
|
||||
status=amo.STATUS_NULL)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon5,
|
||||
pending_info_request=self.days_ago(42))
|
||||
|
||||
# Invisible (user-disabled) addon with expired info request.
|
||||
addon6 = addon_factory(name=u'Incomplete Addön 5',
|
||||
status=amo.STATUS_APPROVED,
|
||||
disabled_by_user=True)
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=addon6,
|
||||
pending_info_request=self.days_ago(42))
|
||||
|
||||
self.expected_addons = [addon2, addon1]
|
||||
|
||||
def test_results_no_permission(self):
|
||||
# Addon reviewer doesn't have access.
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 403
|
||||
|
||||
# Regular user doesn't have access.
|
||||
self.client.logout()
|
||||
assert self.client.login(email='regular@mozilla.com')
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_results(self):
|
||||
self.grant_permission(self.user, 'Reviews:Admin')
|
||||
self.generate_files()
|
||||
self._test_results()
|
||||
|
||||
|
||||
class TestContentReviewQueue(QueueTest):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -2465,11 +2359,11 @@ class TestContentReviewQueue(QueueTest):
|
|||
def test_results(self):
|
||||
self.login_with_permission()
|
||||
self.generate_files()
|
||||
with self.assertNumQueries(27):
|
||||
# 27 queries is a lot, but it used to be much much worse.
|
||||
with self.assertNumQueries(26):
|
||||
# 26 queries is a lot, but it used to be much much worse.
|
||||
# - 2 for savepoints because we're in tests
|
||||
# - 2 for user/groups
|
||||
# - 12 for various queue counts, including current one
|
||||
# - 11 for various queue counts, including current one
|
||||
# (unfortunately duplicated because it appears in two
|
||||
# completely different places)
|
||||
# - 3 for the addons in the queues and their files (regardless of
|
||||
|
@ -2494,7 +2388,7 @@ class TestContentReviewQueue(QueueTest):
|
|||
self.generate_files()
|
||||
|
||||
self._test_queue_layout(
|
||||
'Content Review', tab_position=0, total_addons=5, total_queues=3)
|
||||
'Content Review', tab_position=0, total_addons=5, total_queues=2)
|
||||
|
||||
def test_pending_rejection_filtered_out(self):
|
||||
self.login_with_permission()
|
||||
|
@ -2628,7 +2522,7 @@ class TestScannersReviewQueue(QueueTest):
|
|||
|
||||
self._test_queue_layout(
|
||||
'Flagged By Scanners',
|
||||
tab_position=2, total_addons=4, total_queues=11, per_page=1)
|
||||
tab_position=2, total_addons=4, total_queues=10, per_page=1)
|
||||
|
||||
|
||||
class TestPendingRejectionReviewQueue(QueueTest):
|
||||
|
@ -3360,7 +3254,7 @@ class TestReview(ReviewBase):
|
|||
assert ActivityLog.objects.filter(
|
||||
action=comment_version.id).count() == 1
|
||||
|
||||
def test_info_requested(self):
|
||||
def test_reviewer_reply(self):
|
||||
response = self.client.post(self.url, {'action': 'reply',
|
||||
'comments': 'hello sailor'})
|
||||
assert response.status_code == 302
|
||||
|
@ -3372,7 +3266,7 @@ class TestReview(ReviewBase):
|
|||
'comments': 'hello sailor'})
|
||||
assert response.status_code == 302
|
||||
|
||||
def test_info_requested_canned_response(self):
|
||||
def test_reviewer_reply_canned_response(self):
|
||||
response = self.client.post(self.url, {'action': 'reply',
|
||||
'comments': 'hello sailor',
|
||||
'canned_response': 'foo'})
|
||||
|
@ -3852,7 +3746,6 @@ class TestReview(ReviewBase):
|
|||
assert not doc('#disable_auto_approval')
|
||||
assert not doc('#enable_auto_approval')
|
||||
assert not doc('#clear_auto_approval_delayed_until')
|
||||
assert not doc('#clear_pending_info_request')
|
||||
assert not doc('#clear_pending_rejections')
|
||||
assert not doc('#deny_resubmission')
|
||||
assert not doc('#allow_resubmission')
|
||||
|
@ -4071,20 +3964,6 @@ class TestReview(ReviewBase):
|
|||
assert not doc('#disable_auto_approval')
|
||||
assert not doc('#enable_auto_approval')
|
||||
|
||||
def test_clear_pending_info_request_as_admin(self):
|
||||
self.login_as_admin()
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content)
|
||||
assert not doc('#clear_pending_info_request')
|
||||
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addon, pending_info_request=self.days_ago(1))
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content)
|
||||
assert doc('#clear_pending_info_request')
|
||||
|
||||
def test_clear_pending_rejections_as_admin(self):
|
||||
self.login_as_admin()
|
||||
response = self.client.get(self.url)
|
||||
|
@ -6457,7 +6336,6 @@ class TestAddonReviewerViewSet(TestCase):
|
|||
def test_patch_flags_change_everything(self):
|
||||
AddonReviewerFlags.objects.create(
|
||||
addon=self.addon,
|
||||
pending_info_request=self.days_ago(1),
|
||||
auto_approval_disabled=True,
|
||||
auto_approval_delayed_until=self.days_ago(42))
|
||||
self.grant_permission(self.user, 'Reviews:Admin')
|
||||
|
@ -6469,7 +6347,6 @@ class TestAddonReviewerViewSet(TestCase):
|
|||
'needs_admin_code_review': True,
|
||||
'needs_admin_content_review': True,
|
||||
'needs_admin_theme_review': True,
|
||||
'pending_info_request': None,
|
||||
}
|
||||
response = self.client.patch(self.flags_url, data)
|
||||
assert response.status_code == 200
|
||||
|
@ -6483,11 +6360,6 @@ class TestAddonReviewerViewSet(TestCase):
|
|||
assert reviewer_flags.needs_admin_code_review is True
|
||||
assert reviewer_flags.needs_admin_content_review is True
|
||||
assert reviewer_flags.needs_admin_theme_review is True
|
||||
assert reviewer_flags.pending_info_request is None
|
||||
assert ActivityLog.objects.count() == 1
|
||||
activity_log = ActivityLog.objects.latest('pk')
|
||||
assert activity_log.action == amo.LOG.ADMIN_ALTER_INFO_REQUEST.id
|
||||
assert activity_log.arguments[0] == self.addon
|
||||
|
||||
def test_deny_resubmission(self):
|
||||
self.grant_permission(self.user, 'Reviews:Admin')
|
||||
|
@ -8270,4 +8142,4 @@ class TestMadQueue(QueueTest):
|
|||
self.grant_permission(self.user, 'Reviews:Admin')
|
||||
|
||||
self._test_queue_layout('Flagged for Human Review', tab_position=2,
|
||||
total_addons=3, total_queues=5, per_page=1)
|
||||
total_addons=3, total_queues=4, per_page=1)
|
||||
|
|
|
@ -31,8 +31,6 @@ urlpatterns = (
|
|||
name='reviewers.queue_scanners'),
|
||||
url(r'queue/pending_rejection', views.queue_pending_rejection,
|
||||
name='reviewers.queue_pending_rejection'),
|
||||
url(r'^queue/expired_info_requests', views.queue_expired_info_requests,
|
||||
name='reviewers.queue_expired_info_requests'),
|
||||
url(r'^unlisted_queue/all$', views.unlisted_list,
|
||||
name='reviewers.unlisted_queue_all'),
|
||||
url(r'^moderationlog$', views.ratings_moderation_log,
|
||||
|
|
|
@ -207,19 +207,6 @@ class ModernAddonQueueTable(ReviewerQueueTable):
|
|||
render_last_content_review = render_last_human_review
|
||||
|
||||
|
||||
class ExpiredInfoRequestsTable(ModernAddonQueueTable):
|
||||
deadline = tables.Column(
|
||||
verbose_name=_(u'Information Request Deadline'),
|
||||
accessor='reviewerflags.pending_info_request')
|
||||
|
||||
class Meta(ModernAddonQueueTable.Meta):
|
||||
fields = ('addon_name', 'flags', 'last_human_review', 'weight',
|
||||
'deadline')
|
||||
|
||||
def render_deadline(self, value):
|
||||
return naturaltime(value) if value else ''
|
||||
|
||||
|
||||
class PendingRejectionTable(ModernAddonQueueTable):
|
||||
deadline = tables.Column(
|
||||
verbose_name=_('Pending Rejection Deadline'),
|
||||
|
|
|
@ -70,8 +70,8 @@ from olympia.reviewers.serializers import (
|
|||
DiffableVersionSerializer, DraftCommentSerializer, FileInfoSerializer,
|
||||
)
|
||||
from olympia.reviewers.utils import (
|
||||
AutoApprovedTable, ContentReviewTable, ExpiredInfoRequestsTable,
|
||||
MadReviewTable, PendingRejectionTable, ReviewHelper, ScannersReviewTable,
|
||||
AutoApprovedTable, ContentReviewTable, MadReviewTable,
|
||||
PendingRejectionTable, ReviewHelper, ScannersReviewTable,
|
||||
ViewUnlistedAllListTable, view_table_factory)
|
||||
from olympia.users.models import UserProfile
|
||||
from olympia.versions.models import Version, VersionReviewerFlags
|
||||
|
@ -267,10 +267,6 @@ def dashboard(request):
|
|||
if view_all or acl.action_allowed(
|
||||
request, amo.permissions.REVIEWS_ADMIN):
|
||||
sections[ugettext('Admin Tools')] = [(
|
||||
ugettext('Expired Information Requests ({0})'.format(
|
||||
queue_counts['expired_info_requests'])),
|
||||
reverse('reviewers.queue_expired_info_requests')
|
||||
), (
|
||||
ugettext('Add-ons Pending Rejection ({0})').format(
|
||||
queue_counts['pending_rejection']),
|
||||
reverse('reviewers.queue_pending_rejection')
|
||||
|
@ -507,13 +503,6 @@ def fetch_queue_counts(admin_reviewer):
|
|||
qs = filter_admin_review_for_legacy_queue(qs)
|
||||
return qs.count
|
||||
|
||||
expired = (
|
||||
Addon.objects.filter(
|
||||
reviewerflags__pending_info_request__lt=datetime.now(),
|
||||
status__in=(amo.STATUS_NOMINATED, amo.STATUS_APPROVED),
|
||||
disabled_by_user=False)
|
||||
.order_by('reviewerflags__pending_info_request'))
|
||||
|
||||
counts = {
|
||||
'extension': construct_query_from_sql_model(
|
||||
ViewExtensionQueue),
|
||||
|
@ -535,7 +524,6 @@ def fetch_queue_counts(admin_reviewer):
|
|||
'scanners': (
|
||||
Addon.objects.get_scanners_queue(
|
||||
admin_reviewer=admin_reviewer).count),
|
||||
'expired_info_requests': expired.count,
|
||||
'pending_rejection': (
|
||||
Addon.objects.get_pending_rejection_queue(
|
||||
admin_reviewer=admin_reviewer).count),
|
||||
|
@ -629,18 +617,6 @@ def queue_auto_approved(request):
|
|||
qs=qs, SearchForm=None)
|
||||
|
||||
|
||||
@permission_required(amo.permissions.REVIEWS_ADMIN)
|
||||
def queue_expired_info_requests(request):
|
||||
qs = (
|
||||
Addon.objects.filter(
|
||||
reviewerflags__pending_info_request__lt=datetime.now(),
|
||||
status__in=(amo.STATUS_NOMINATED, amo.STATUS_APPROVED),
|
||||
disabled_by_user=False)
|
||||
.order_by('reviewerflags__pending_info_request'))
|
||||
return _queue(request, ExpiredInfoRequestsTable, 'expired_info_requests',
|
||||
qs=qs, SearchForm=None)
|
||||
|
||||
|
||||
@permission_or_tools_view_required(amo.permissions.ADDONS_REVIEW)
|
||||
def queue_scanners(request):
|
||||
admin_reviewer = is_admin_reviewer(request)
|
||||
|
@ -1280,9 +1256,6 @@ class AddonReviewerViewSet(GenericViewSet):
|
|||
serializer = AddonReviewerFlagsSerializer(
|
||||
instance, data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
# If pending info request was modified, log it.
|
||||
if 'pending_info_request' in serializer.initial_data:
|
||||
ActivityLog.create(amo.LOG.ADMIN_ALTER_INFO_REQUEST, addon)
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче