Remove 'No Restart' label, instead show 'Requires Restart' where necessary

The no_restart boolean field on File is kept as-is for now to avoid
a migration.
This commit is contained in:
Mathieu Pillard 2016-07-28 16:02:54 +02:00
Родитель 7cc003c5e9
Коммит b1ab28d1b0
24 изменённых файлов: 118 добавлений и 178 удалений

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

@ -1274,10 +1274,11 @@ class Addon(OnChangeMixin, ModelBase):
def featured_random(cls, app, lang):
return get_featured_ids(app, lang)
def is_no_restart(self):
"""Is this a no-restart add-on?"""
@property
def requires_restart(self):
"""Whether the add-on requires a browser restart to work."""
files = self.current_version and self.current_version.all_files
return bool(files and files[0].no_restart)
return bool(files and files[0].requires_restart)
def is_featured(self, app, lang=None):
"""Is add-on globally featured for this app and language?"""

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

@ -5,9 +5,9 @@
<div id="addon-summary" class="primary">
<p{{ addon.summary|locale_html }}>{{ addon.summary|nl2br }}</p>
{% with files = addon.current_version.all_files %}
{% if files and files[0].no_restart %}
<div id="no-restart" class="js-hidden">
<span id="no-restart-msg">{{ _('No restart required') }}</span>
{% if files and files[0].requires_restart %}
<div id="requires-restart" class="js-hidden">
<span id="requires-restart-msg">{{ _('Requires Restart') }}</span>
</div>
{% endif %}
{% endwith %}

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

@ -95,8 +95,8 @@
{% if version %}
<span class="version-number" itemprop="version">{{ version.version }}</span>
{% endif %}
{% if addon.is_no_restart() %}
&nbsp;<span class="no-restart">{{ _('No Restart') }}</span>
{% if addon.requires_restart %}
&nbsp;<span class="requires-restart">{{ _('Requires Restart') }}</span>
{% endif %}
</h1>
<h4 class="author">{{ _('by') }} {{ users_list(addon.listed_authors) }}</h4>

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

@ -10,8 +10,8 @@
{% endif %}
<a href="{{ dl_url }}">
<img src="{{ addon.icon_url }}" alt="">{{ addon.name }}</a>
{% if addon.is_no_restart() %}
<span class="no-restart">{{ _('No Restart') }}</span>
{% if addon.requires_restart %}
<span class="requires-restart">{{ _('Requires Restart') }}</span>
{% endif %}
{% if addon.is_featured(request.APP, request.LANG) %}
<span class="featured">{{ _('Featured') }}</span>

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

@ -209,7 +209,7 @@ class TestTagsForm(TestCase):
def add_restricted(self, *args):
if not args:
args = ['restartless']
args = ['i_am_a_restricted_tag']
for arg in args:
tag = Tag.objects.create(tag_text=arg, restricted=True)
AddonTag.objects.create(tag=tag, addon=self.addon)
@ -221,42 +221,43 @@ class TestTagsForm(TestCase):
instance=self.addon)
assert form.fields['tags'].initial == 'bar, foo'
assert self.get_tag_text() == ['bar', 'foo', 'restartless']
assert self.get_tag_text() == ['bar', 'foo', 'i_am_a_restricted_tag']
self.add_tags('')
assert self.get_tag_text() == ['restartless']
assert self.get_tag_text() == ['i_am_a_restricted_tag']
def test_tags_error(self):
self.add_restricted('restartless', 'sdk')
self.add_restricted('i_am_a_restricted_tag', 'sdk')
data = self.data.copy()
data.update({'tags': 'restartless'})
data.update({'tags': 'i_am_a_restricted_tag'})
form = forms.AddonFormBasic(data=data, request=self.request,
instance=self.addon)
assert form.errors['tags'][0] == (
'"restartless" is a reserved tag and cannot be used.')
data.update({'tags': 'restartless, sdk'})
'"i_am_a_restricted_tag" is a reserved tag and cannot be used.')
data.update({'tags': 'i_am_a_restricted_tag, sdk'})
form = forms.AddonFormBasic(data=data, request=self.request,
instance=self.addon)
assert form.errors['tags'][0] == (
'"restartless", "sdk" are reserved tags and cannot be used.')
'"i_am_a_restricted_tag", "sdk" are reserved tags and'
' cannot be used.')
@patch('olympia.access.acl.action_allowed')
def test_tags_admin_restricted(self, action_allowed):
action_allowed.return_value = True
self.add_restricted('restartless')
self.add_restricted('i_am_a_restricted_tag')
self.add_tags('foo, bar')
assert self.get_tag_text() == ['bar', 'foo']
self.add_tags('foo, bar, restartless')
self.add_tags('foo, bar, i_am_a_restricted_tag')
assert self.get_tag_text() == ['bar', 'foo', 'restartless']
assert self.get_tag_text() == ['bar', 'foo', 'i_am_a_restricted_tag']
form = forms.AddonFormBasic(data=self.data, request=self.request,
instance=self.addon)
assert form.fields['tags'].initial == 'bar, foo, restartless'
assert form.fields['tags'].initial == 'bar, foo, i_am_a_restricted_tag'
@patch('olympia.access.acl.action_allowed')
def test_tags_admin_restricted_count(self, action_allowed):
action_allowed.return_value = True
self.add_restricted()
self.add_tags('restartless, %s' % (', '.join('tag-test-%s' %
self.add_tags('i_am_a_restricted_tag, %s' % (', '.join('tag-test-%s' %
i for i in range(0, 20))))
def test_tags_restricted_count(self):

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

@ -739,18 +739,19 @@ class TestAddonModels(TestCase):
addon.status = amo.STATUS_UNREVIEWED
assert not addon.is_reviewed()
def test_is_no_restart(self):
a = Addon.objects.get(pk=3615)
f = a.current_version.all_files[0]
assert not f.no_restart
assert not a.is_no_restart()
def test_requires_restart(self):
addon = Addon.objects.get(pk=3615)
file_ = addon.current_version.all_files[0]
assert not file_.no_restart
assert file_.requires_restart
assert addon.requires_restart
f.update(no_restart=True)
assert Addon.objects.get(pk=3615).is_no_restart()
file_.update(no_restart=True)
assert not Addon.objects.get(pk=3615).requires_restart
a.versions.all().delete()
a._current_version = None
assert not a.is_no_restart()
addon.versions.all().delete()
addon._current_version = None
assert not addon.requires_restart
def test_is_featured(self):
"""Test if an add-on is globally featured"""

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

@ -843,18 +843,19 @@ class TestDetailPage(TestCase):
self.addon.save()
assert pq(self.client.get(self.url).content)(selector)
def test_no_restart(self):
no_restart = '<span class="no-restart">No Restart</span>'
def test_requires_restart(self):
span_restart = '<span class="requires-restart">Requires Restart</span>'
f = self.addon.current_version.all_files[0]
assert not f.no_restart
assert f.requires_restart is True
r = self.client.get(self.url)
assert no_restart not in r.content
assert span_restart in r.content
f.no_restart = True
f.save()
assert f.requires_restart is False
r = self.client.get(self.url)
self.assertContains(r, no_restart)
assert span_restart not in r.content
def test_disabled_user_message(self):
self.addon.update(disabled_by_user=True)
@ -993,12 +994,13 @@ class TestImpalaDetailPage(TestCase):
assert d.find('.install-button a').attr('href').endswith(
'?src=dp-hc-dependencies')
def test_no_restart(self):
def test_requires_restart(self):
f = self.addon.current_version.all_files[0]
assert not f.no_restart
assert self.get_pq()('.no-restart').length == 0
assert f.requires_restart
assert self.get_pq()('.requires-restart').length == 1
f.update(no_restart=True)
assert self.get_pq()('.no-restart').length == 1
assert not f.requires_restart
assert self.get_pq()('.requires-restart').length == 0
def test_license_link_builtin(self):
g = 'http://google.com'

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

@ -443,13 +443,14 @@ class TestEditBasic(TestEdit):
def test_edit_restricted_tags(self):
addon = self.get_addon()
tag = Tag.objects.create(tag_text='restartless', restricted=True)
tag = Tag.objects.create(
tag_text='i_am_a_restricted_tag', restricted=True)
AddonTag.objects.create(tag=tag, addon=addon)
res = self.client.get(self.basic_edit_url)
divs = pq(res.content)('#addon_tags_edit .edit-addon-details')
assert len(divs) == 2
assert 'restartless' in divs.eq(1).text()
assert 'i_am_a_restricted_tag' in divs.eq(1).text()
def test_text_not_none_when_has_flags(self):
r = self.client.get(self.url)

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

@ -166,8 +166,8 @@ class ViewQueue(RawSQLModel):
return self._explode_concat(self._application_ids)
@property
def is_traditional_restartless(self):
return self.is_restartless and not self.is_jetpack
def requires_restart(self):
return not self.is_restartless
@property
def sources_provided(self):
@ -178,8 +178,8 @@ class ViewQueue(RawSQLModel):
props = (
('admin_review', 'admin-review', _lazy('Admin Review')),
('is_jetpack', 'jetpack', _lazy('Jetpack Add-on')),
('is_traditional_restartless', 'restartless',
_lazy('Restartless Add-on')),
('requires_restart', 'requires_restart',
_lazy('Requires Restart')),
('has_info_request', 'info', _lazy('More Information Requested')),
('has_editor_comment', 'editor', _lazy('Contains Editor Comment')),
('sources_provided', 'sources-provided',

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

@ -47,9 +47,15 @@ def create_addon_file(name, version_str, addon_status, file_status,
vr.update(**version_kw)
va, created_ = ApplicationsVersions.objects.get_or_create(
version=vr, application=application.id, min=app_vr, max=app_vr)
file_ = File.objects.create(version=vr, filename=u"%s.xpi" % name,
platform=platform.id, status=file_status,
**file_kw)
file_defaults = {
'version': vr,
'filename': u"%s.xpi" % name,
'platform': platform.id,
'status': file_status,
'no_restart': True
}
file_defaults.update(file_kw)
file_ = File.objects.create(**file_defaults)
if created:
vr.update(created=created)
file_.update(created=created)
@ -181,18 +187,18 @@ class TestPendingQueue(TestQueue):
q = self.Queue.objects.get()
assert q.flags == [('editor', 'Contains Editor Comment')]
def test_flags_jetpack_and_restartless(self):
def test_flags_jetpack(self):
self.new_file(version=u'0.1', file_kw={'jetpack_version': '1.8',
'no_restart': True})
q = self.Queue.objects.get()
assert q.flags == [('jetpack', 'Jetpack Add-on')]
def test_flags_restartless(self):
self.new_file(version=u'0.1', file_kw={'no_restart': True})
def test_flags_requires_restart(self):
self.new_file(version=u'0.1', file_kw={'no_restart': False})
q = self.Queue.objects.get()
assert q.flags == [('restartless', 'Restartless Add-on')]
assert q.flags == [('requires_restart', 'Requires Restart')]
def test_flags_sources_provided(self):
f = self.new_file(version=u'0.1')

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

@ -890,13 +890,25 @@ class TestQueueBasics(QueueTest):
assert rows.attr('data-addon') == str(ad['addon'].id)
assert rows.find('td').eq(1).text() == 'Jetpack 0.1'
assert rows.find('.ed-sprite-jetpack').length == 1
assert rows.find('.ed-sprite-restartless').length == 0
def test_flags_restartless(self):
ad = create_addon_file('Restartless', '0.1', amo.STATUS_NOMINATED,
amo.STATUS_UNREVIEWED)
ad_file = ad['version'].files.all()[0]
ad_file.update(no_restart=True)
def test_flags_requires_restart(self):
ad = create_addon_file('Some Add-on', '0.1', amo.STATUS_NOMINATED,
amo.STATUS_UNREVIEWED,
file_kw={'no_restart': False})
r = self.client.get(reverse('editors.queue_nominated'))
rows = pq(r.content)('#addon-queue tr.addon-row')
assert rows.length == 1
assert rows.attr('data-addon') == str(ad['addon'].id)
assert rows.find('td').eq(1).text() == 'Some Add-on 0.1'
assert rows.find('.ed-sprite-jetpack').length == 0
assert rows.find('.ed-sprite-requires_restart').length == 1
def test_flags_no_restart(self):
# create_addon_file() creates restartless files by default.
ad = create_addon_file('Restartless', '0.1',
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED)
r = self.client.get(reverse('editors.queue_nominated'))
@ -905,24 +917,7 @@ class TestQueueBasics(QueueTest):
assert rows.attr('data-addon') == str(ad['addon'].id)
assert rows.find('td').eq(1).text() == 'Restartless 0.1'
assert rows.find('.ed-sprite-jetpack').length == 0
assert rows.find('.ed-sprite-restartless').length == 1
def test_flags_restartless_and_jetpack(self):
ad = create_addon_file('Restartless Jetpack', '0.1',
amo.STATUS_NOMINATED, amo.STATUS_UNREVIEWED)
ad_file = ad['version'].files.all()[0]
ad_file.update(jetpack_version=1.2, no_restart=True)
r = self.client.get(reverse('editors.queue_nominated'))
rows = pq(r.content)('#addon-queue tr.addon-row')
assert rows.length == 1
assert rows.attr('data-addon') == str(ad['addon'].id)
assert rows.find('td').eq(1).text() == 'Restartless Jetpack 0.1'
# Show only jetpack if it's both.
assert rows.find('.ed-sprite-jetpack').length == 1
assert rows.find('.ed-sprite-restartless').length == 0
assert rows.find('.ed-sprite-requires_restart').length == 0
def test_theme_redirect(self):
users = []
@ -1968,11 +1963,13 @@ class TestReview(ReviewBase):
assert self.client.head(self.url).status_code == 200
def test_not_flags(self):
self.addon.current_version.files.update(no_restart=True)
response = self.client.get(self.url)
assert response.status_code == 200
assert len(response.context['flags']) == 0
def test_flags(self):
def test_flag_admin_review(self):
self.addon.current_version.files.update(no_restart=True)
self.addon.update(admin_review=True)
response = self.client.get(self.url)
assert len(response.context['flags']) == 1

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

@ -29,7 +29,6 @@ from olympia.amo.urlresolvers import reverse
from olympia.amo.helpers import user_media_path, user_media_url
from olympia.applications.models import AppVersion
from olympia.files.utils import SafeUnzip, write_crx_as_xpi
from olympia.tags.models import Tag
log = commonware.log.getLogger('z.files')
@ -111,6 +110,13 @@ class File(OnChangeMixin, ModelBase):
return (self.version.addon.automated_signing or
self.status == amo.STATUS_BETA)
@property
def requires_restart(self):
"""Whether the add-on file requires a browser restart to work."""
# For historical purposes the field we store is "no_restart", which
# is exactly the opposite.
return not self.no_restart
def is_mirrorable(self):
return self.status in amo.MIRROR_STATUSES
@ -158,8 +164,6 @@ class File(OnChangeMixin, ModelBase):
data = cls.get_jetpack_metadata(upload.path)
if 'sdkVersion' in data and data['sdkVersion']:
file_.jetpack_version = data['sdkVersion'][:10]
if file_.jetpack_version:
Tag(tag_text='jetpack').save_tag(addon)
file_.no_restart = parse_data.get('no_restart', False)
file_.strict_compatibility = parse_data.get('strict_compatibility',
False)

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

@ -922,14 +922,12 @@ class TestFileFromUpload(UploadTest):
f = File.from_upload(upload, self.version, self.platform)
file_ = File.objects.get(id=f.id)
assert file_.jetpack_version == '1.0b4'
assert ['jetpack'] == [t.tag_text for t in self.addon.tags.all()]
def test_jetpack_with_invalid_json(self):
upload = self.upload('jetpack_invalid')
f = File.from_upload(upload, self.version, self.platform)
file_ = File.objects.get(id=f.id)
assert file_.jetpack_version is None
assert not self.addon.tags.exists()
def test_filename(self):
upload = self.upload('jetpack')
@ -1057,7 +1055,6 @@ class TestFileFromUpload(UploadTest):
upload = self.upload('extension.xpi')
f = File.from_upload(upload, self.version, self.platform)
assert f.filename.endswith('.xpi')
assert not self.addon.tags.exists()
def test_langpack_extension(self):
upload = self.upload('langpack.xpi')

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

@ -222,7 +222,7 @@ class TestManifestJSONExtractor(TestCase):
assert self.parse({})['type'] == amo.ADDON_EXTENSION
def test_no_restart(self):
"""manifest.json addons are always no-restart."""
"""manifest.json addons are always no_restart."""
assert self.parse({})['no_restart'] is True
def test_name(self):

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

@ -14,9 +14,9 @@
</li>
<li><a href="{{ url('addons.detail', addon.slug, src='discovery-learnmore') }}"
id="learn-more" class="button">{{ _('Learn More') }}</a></li>
{% if addon.is_no_restart() %}
<li id="no-restart">
<span id="no-restart-msg">{{ _('No restart required') }}</span>
{% if addon.requires_restart %}
<li id="requires-restart">
<span id="requires-restart-msg">{{ _('Requires Restart') }}</span>
</li>
{% endif %}
{% if addon.privacy_policy %}

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

@ -347,14 +347,15 @@ class TestDetails(TestCase):
def get_addon(self):
return Addon.objects.get(id=3615)
def test_no_restart(self):
def test_requires_restart(self):
f = self.addon.current_version.all_files[0]
assert not f.no_restart
assert f.requires_restart
r = self.client.get(self.detail_url)
assert pq(r.content)('#no-restart').length == 0
assert pq(r.content)('#requires-restart').length == 1
f.update(no_restart=True)
assert not f.requires_restart
r = self.client.get(self.detail_url)
assert pq(r.content)('#no-restart').length == 1
assert pq(r.content)('#requires-restart').length == 0
def test_install_button_eula(self):
doc = pq(self.client.get(self.detail_url).content)
@ -444,9 +445,9 @@ class TestDownloadSources(TestCase):
doc = pq(r.content)
assert doc('#install a.download').attr('href').endswith(
'?src=discovery-details')
assert doc('#install li:eq(1)').find('a').attr('href').endswith(
assert doc('#install li a#learn-more').attr('href').endswith(
'?src=discovery-learnmore')
assert doc('#install li:eq(2)').find('a').attr('href').endswith(
assert doc('#install li.privacy a').attr('href').endswith(
'?src=discovery-learnmore')
def test_detail_trickle(self):

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

@ -1,31 +0,0 @@
import cronjobs
from olympia.addons.models import Addon
from olympia.tags.models import AddonTag, Tag
@cronjobs.register
def tag_jetpacks():
# A temporary solution for singling out jetpacks on AMO. See bug 580827
tags = (
('jetpack', {
'_current_version__files__jetpack_version__isnull': False
}),
('restartless', {
'_current_version__files__no_restart': True
})
)
qs = Addon.objects.values_list('id', flat=True)
for tag, q in tags:
tag = Tag.objects.get(tag_text=tag)
for addon in set(qs.filter(**q).exclude(tags=tag)):
AddonTag.objects.create(addon_id=addon, tag=tag)
d = {}
for k, v in q.items():
# Reverse the sense of the argument and use `.filter()`.
# `.exclude()` does not work as expected here, for some reason.
d['addon__%s' % k] = not v
AddonTag.objects.filter(tag=tag).filter(**d).delete()

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

@ -1,40 +0,0 @@
from olympia.amo.tests import TestCase
from olympia.addons.models import Addon
from olympia.files.models import File
from olympia.tags.models import Tag, AddonTag
from olympia.tags import cron
class TestTagJetpacks(TestCase):
fixtures = ['base/apps', 'base/addon_3615']
def setUp(self):
Tag.objects.create(tag_text='jetpack')
Tag.objects.create(tag_text='restartless')
AddonTag.objects.all().delete()
self.addon = Addon.objects.get(id=3615)
def test_jetpack(self):
File.objects.update(jetpack_version='1.0')
cron.tag_jetpacks()
assert ['jetpack'] == [t.tag_text for t in self.addon.tags.all()]
def test_restartless(self):
File.objects.update(no_restart=True)
cron.tag_jetpacks()
assert ['restartless'] == [t.tag_text for t in self.addon.tags.all()]
def test_no_change(self):
File.objects.update(no_restart=False, jetpack_version=None)
cron.tag_jetpacks()
assert [] == [t.tag_text for t in self.addon.tags.all()]
def test_reverse(self):
File.objects.update(no_restart=True, jetpack_version='1.0')
cron.tag_jetpacks()
assert ['jetpack', 'restartless'] == (
sorted([t.tag_text for t in self.addon.tags.all()]))
File.objects.update(no_restart=False, jetpack_version=None)
cron.tag_jetpacks()
assert [] == [t.tag_text for t in self.addon.tags.all()]

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

@ -476,7 +476,7 @@ h1.addon,
}
}
span.no-restart,
span.requires-restart,
span.featured {
background-color: #e8933a;
border-radius: 2px;
@ -492,7 +492,7 @@ span.featured {
background-color: #093;
}
.html-rtl span.no-restart,
.html-rtl span.requires-restart,
.html-rtl span.featured {
margin: 4px 4px 0 0;
}

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

@ -848,11 +848,11 @@ p#back:hover:before {
display: none;
}
li#no-restart {
li#requires-restart {
padding: 0;
}
#no-restart-msg {
#requires-restart-msg {
background: #fff;
border: 1px solid #B6D9E5;
border-radius: 3px;

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

@ -25,7 +25,7 @@
.ed-sprite-android { background-position: 0 -48px; }
.ed-sprite-mobile { background-position: 0 -64px; }
.ed-sprite-jetpack { background-position: 0 -80px; }
.ed-sprite-restartless { background-position: 0 -96px; }
.ed-sprite-requires_restart { background-position: 0 -96px; }
.ed-sprite-notes { background-position: 0 -112px; }
.ed-sprite-admin-review { background-position: 0 -128px; }
.ed-sprite-editor { background-position: 0 -144px; }

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

@ -3667,11 +3667,11 @@ input.ui-autocomplete-loading {
text-decoration: underline;
}
#no-restart {
#requires-restart {
margin: -1em 0 1.5em 0;
}
#no-restart-msg {
#requires-restart-msg {
border: 1px solid #b6d9e5;
border-radius: 3px; /* Only shown in Fx4. */
padding: .2em .4em;

Двоичные данные
static/img/developers/editor-sprite.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 7.1 KiB

После

Ширина:  |  Высота:  |  Размер: 7.0 KiB

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

@ -26,10 +26,10 @@ $(document).ready(function() {
var etiquette_box = $("#addons-display-review-etiquette").hide();
$("#short-review").focus(function() { etiquette_box.show("fast"); } );
/* No restart required box. (Only shown in Fx4+). */
var no_restart = $('#addon-summary #no-restart');
if (no_restart.length && z.browser.firefox
/* Restart required box. (Only shown in Fx4+). */
var requires_restart = $('#addon-summary #requires-restart');
if (requires_restart.length && z.browser.firefox
&& VersionCompare.compareVersions(z.browserVersion, '4.0a1') > 0) {
no_restart.show();
requires_restart.show();
}
});