Generate static theme Preview on version submit (#7475)
This commit is contained in:
Родитель
8be6a7b606
Коммит
c05e4cfdf1
|
@ -41,6 +41,7 @@ addons:
|
|||
- oracle-java8-set-default
|
||||
- elasticsearch
|
||||
- gettext
|
||||
- librsvg2-bin
|
||||
|
||||
before_install:
|
||||
- mysql -e 'create database olympia;'
|
||||
|
|
|
@ -40,6 +40,8 @@ RUN apt-get update && apt-get install -y \
|
|||
default-libmysqlclient-dev \
|
||||
swig \
|
||||
gettext \
|
||||
# Use rsvg-convert to render our static theme previews
|
||||
librsvg2-bin \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Compile required locale
|
||||
|
|
|
@ -43,6 +43,8 @@ RUN apt-get update && apt-get install -y \
|
|||
default-libmysqlclient-dev \
|
||||
swig \
|
||||
gettext \
|
||||
# Use rsvg-convert to render our static theme previews
|
||||
librsvg2-bin \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Compile required locale
|
||||
|
|
|
@ -522,7 +522,7 @@ class Addon(OnChangeMixin, ModelBase):
|
|||
return True
|
||||
|
||||
@classmethod
|
||||
def initialize_addon_from_upload(cls, data, upload, channel):
|
||||
def initialize_addon_from_upload(cls, data, upload, channel, user):
|
||||
fields = [field.name for field in cls._meta.get_fields()]
|
||||
guid = data.get('guid')
|
||||
old_guid_addon = None
|
||||
|
@ -561,23 +561,20 @@ class Addon(OnChangeMixin, ModelBase):
|
|||
if old_guid_addon:
|
||||
old_guid_addon.update(guid='guid-reused-by-pk-{}'.format(addon.pk))
|
||||
old_guid_addon.save()
|
||||
return addon
|
||||
|
||||
@classmethod
|
||||
def create_addon_from_upload_data(cls, data, upload, channel, user=None,
|
||||
**kwargs):
|
||||
addon = cls.initialize_addon_from_upload(data, upload, channel,
|
||||
**kwargs)
|
||||
if user:
|
||||
AddonUser(addon=addon, user=user).save()
|
||||
return addon
|
||||
|
||||
@classmethod
|
||||
def from_upload(cls, upload, platforms, source=None,
|
||||
channel=amo.RELEASE_CHANNEL_LISTED, parsed_data=None):
|
||||
channel=amo.RELEASE_CHANNEL_LISTED, parsed_data=None,
|
||||
user=None):
|
||||
if not parsed_data:
|
||||
parsed_data = parse_addon(upload)
|
||||
|
||||
addon = cls.initialize_addon_from_upload(parsed_data, upload, channel)
|
||||
addon = cls.initialize_addon_from_upload(
|
||||
parsed_data, upload, channel, user)
|
||||
|
||||
if upload.validation_timeout:
|
||||
AddonReviewerFlags.objects.update_or_create(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import re
|
||||
from collections import namedtuple
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
@ -230,6 +231,8 @@ ADDON_ICON_SIZES = [32, 48, 64, 128, 256, 512]
|
|||
# Preview upload sizes [thumb, full]
|
||||
ADDON_PREVIEW_SIZES = [(200, 150), (700, 525)]
|
||||
|
||||
THEME_PREVIEW_SIZE = namedtuple('SizeTuple', 'width height')(680, 100)
|
||||
|
||||
# Persona image sizes [preview, full]
|
||||
PERSONA_IMAGE_SIZES = {
|
||||
'header': [(680, 100), (3000, 200)],
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
{% autoescape true %}
|
||||
<svg id="preview-svg-root" width="680" height="100" xmlns="http://www.w3.org/2000/svg"
|
||||
<svg id="preview-svg-root" width="{{ amo.THEME_PREVIEW_SIZE.width }}" height="{{amo.THEME_PREVIEW_SIZE.height }}" xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:svgjs="http://svgjs.com/svgjs" font-size="16px" font-family="Helvetica, Arial, sans-serif">
|
||||
<defs id="SvgDefs1007"></defs>
|
||||
<rect id="SvgRect1008" width="680" height="100"
|
||||
<rect id="SvgRect1008" width="{{ amo.THEME_PREVIEW_SIZE.width }}" height="{{ amo.THEME_PREVIEW_SIZE.height }}"
|
||||
class="accentcolor" fill="{{ accentcolor|d('rgba(229,230,232,1)') }}" data-fill="rgba(229,230,232,1)"></rect>
|
||||
<image id="svg-header-img" width="680" height="200" preserveAspectRatio="xMaxYMin slice"
|
||||
<image id="svg-header-img" width="{{ amo.THEME_PREVIEW_SIZE.width }}" height="{{ header_src_height|d(amo.THEME_PREVIEW_SIZE.height) }}" preserveAspectRatio="{{ preserve_aspect_ratio|d('xMaxYMin slice') }}"
|
||||
xlink:href="{{ header_src|d }}"></image>
|
||||
<text id="SvgText1012" x="200" y="25" class="textcolor" fill="{{ textcolor|d }}">
|
||||
<tspan id="SvgTspan1013" dy="2">🌐 .... ....</tspan>
|
||||
<tspan id="SvgTspan1013" dy="2">. .... ....</tspan>
|
||||
<tspan id="SvgTspan1013x" dy="5" dx="45" font-size="150%">×</tspan>
|
||||
</text>
|
||||
<text id="SvgText1012a" x="340" y="25" class="textcolor" fill="{{ textcolor|d }}">
|
||||
<tspan id="SvgTspan1024a" dy="2">🌐 .... ....</tspan>
|
||||
<tspan id="SvgTspan1024a" dy="2">. .... ....</tspan>
|
||||
<tspan id="SvgTspan1024ax" dy="5" dx="45" font-size="150%">×</tspan>
|
||||
</text>
|
||||
<text id="SvgText1012b" x="480" y="25" class="textcolor" fill="{{ textcolor|d }}">
|
||||
|
@ -20,45 +20,46 @@
|
|||
</text>
|
||||
<path d="M330,0 v45 h1 v-45 h-1 z" class="textcolor" fill="{{ text_color|d }}" fill-opacity="0.6"></path>
|
||||
<path d="M470,0 v45 h1 v-45 h-1 z" class="textcolor" fill="{{ text_color|d }}" fill-opacity="0.6"></path>
|
||||
<rect id="SvgRect1014" width="680" height="55" y="45"
|
||||
<rect id="SvgRect1014" width="{{ amo.THEME_PREVIEW_SIZE.width }}" height="55" y="45"
|
||||
class="toolbar" fill="{{ toolbar|d('rgba(255,255,255,0.6)') }}" data-fill="rgba(255,255,255,0.6)"></rect>
|
||||
<rect id="SvgRect1015" width="140" height="42" x="50" y="3"
|
||||
class="toolbar" fill="{{ toolbar|d('rgba(255,255,255,0.6)') }}" data-fill="rgba(255,255,255,0.6)"></rect>
|
||||
<text id="SvgText1016" x="60" y="25"
|
||||
class="toolbar_text" fill="{{ toolbar_text|d }}">
|
||||
<tspan id="SvgTspan1017" dy="2">🌐 .... .... </tspan>
|
||||
<tspan id="SvgTspan1017" dy="2">. .... .... </tspan>
|
||||
<tspan id="SvgTspan1013x" dy="5" dx="45" font-size="150%">×</tspan>
|
||||
</text>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" x="10" y="65" class="toolbar_text" fill="{{ toolbar_text|d }}">
|
||||
<path d="M15 7H3.414l4.293-4.293a1 1 0 0 0-1.414-1.414l-6 6a1 1 0 0 0 0 1.414l6 6a1 1 0 0 0 1.414-1.414L3.414 9H15a1 1 0 0 0 0-2z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" x="10" y="65" id="button-back-arrow-svg">
|
||||
<path d="M15 7H3.414l4.293-4.293a1 1 0 0 0-1.414-1.414l-6 6a1 1 0 0 0 0 1.414l6 6a1 1 0 0 0 1.414-1.414L3.414 9H15a1 1 0 0 0 0-2z" class="toolbar_text" fill="{{ toolbar_text|d }}" ></path>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" x="50" y="65" class="toolbar_text" fill="{{ toolbar_text|d }}">
|
||||
<path d="M15.707 7.293l-6-6a1 1 0 0 0-1.414 1.414L12.586 7H1a1 1 0 0 0 0 2h11.586l-4.293 4.293a1 1 0 1 0 1.414 1.414l6-6a1 1 0 0 0 0-1.414z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" x="50" y="65" id="button-forward-arrow-svg">
|
||||
<path d="M15.707 7.293l-6-6a1 1 0 0 0-1.414 1.414L12.586 7H1a1 1 0 0 0 0 2h11.586l-4.293 4.293a1 1 0 1 0 1.414 1.414l6-6a1 1 0 0 0 0-1.414z" class="toolbar_text" fill="{{ toolbar_text|d }}" ></path>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" x="90" y="65" class="toolbar_text" fill="{{ toolbar_text|d }}">
|
||||
<path d="M15 1a1 1 0 0 0-1 1v2.418A6.995 6.995 0 1 0 8 15a6.954 6.954 0 0 0 4.95-2.05 1 1 0 0 0-1.414-1.414A5.019 5.019 0 1 1 12.549 6H10a1 1 0 0 0 0 2h5a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" x="90" y="65" id="button-refresh-svg">
|
||||
<path d="M15 1a1 1 0 0 0-1 1v2.418A6.995 6.995 0 1 0 8 15a6.954 6.954 0 0 0 4.95-2.05 1 1 0 0 0-1.414-1.414A5.019 5.019 0 1 1 12.549 6H10a1 1 0 0 0 0 2h5a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1z" class="toolbar_text" fill="{{ toolbar_text|d }}" ></path>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" x="130" y="65" class="toolbar_text" fill="{{ toolbar_text|d }}">
|
||||
<path d="M15.7 7.3l-7-7c-.4-.4-1-.4-1.4 0l-7 7c-.4.4-.4 1 0 1.4.4.4 1 .4 1.4 0l.3-.3V13c0 1.7 1.3 3 3 3h6c1.7 0 3-1.3 3-3V8.4l.3.3c.2.2.4.3.7.3.3 0 .5-.1.7-.3.4-.4.4-1 0-1.4zM8 11.5c0-.3.2-.5.5-.5s.5.2.5.5-.2.5-.5.5-.5-.2-.5-.5zm4 1.5c0 .6-.4 1-1 1h-1V9c0-.6-.4-1-1-1H7c-.6 0-1 .4-1 1v5H5c-.6 0-1-.4-1-1V6.4l4-4 4 4V13z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" x="130" y="65" id="button-home-svg">
|
||||
<path d="M15.7 7.3l-7-7c-.4-.4-1-.4-1.4 0l-7 7c-.4.4-.4 1 0 1.4.4.4 1 .4 1.4 0l.3-.3V13c0 1.7 1.3 3 3 3h6c1.7 0 3-1.3 3-3V8.4l.3.3c.2.2.4.3.7.3.3 0 .5-.1.7-.3.4-.4.4-1 0-1.4zM8 11.5c0-.3.2-.5.5-.5s.5.2.5.5-.2.5-.5.5-.5-.2-.5-.5zm4 1.5c0 .6-.4 1-1 1h-1V9c0-.6-.4-1-1-1H7c-.6 0-1 .4-1 1v5H5c-.6 0-1-.4-1-1V6.4l4-4 4 4V13z" class="toolbar_text" fill="{{ toolbar_text|d }}"></path>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" x="610" y="65" class="toolbar_text" fill="{{ toolbar_text|d }}">
|
||||
<path d="M13 1H3a3.007 3.007 0 0 0-3 3v8a3.009 3.009 0 0 0 3 3h10a3.005 3.005 0 0 0 3-3V4a3.012 3.012 0 0 0-3-3zM2 12V4a1 1 0 0 1 1-1h5v10H3a1 1 0 0 1-1-1zm12 0a1 1 0 0 1-1 1H9V3h4a1 1 0 0 1 1 1z"></path><path d="M12.5 5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 0 1zm0 2h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 0 1zm-1 2h-1a.5.5 0 0 1 0-1h1a.5.5 0 0 1 0 1z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" x="610" y="65" id="button-library-svg">
|
||||
<path d="M13 1H3a3.007 3.007 0 0 0-3 3v8a3.009 3.009 0 0 0 3 3h10a3.005 3.005 0 0 0 3-3V4a3.012 3.012 0 0 0-3-3zM2 12V4a1 1 0 0 1 1-1h5v10H3a1 1 0 0 1-1-1zm12 0a1 1 0 0 1-1 1H9V3h4a1 1 0 0 1 1 1z" class="toolbar_text" fill="{{ toolbar_text|d }}"></path>
|
||||
<path d="M12.5 5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 0 1zm0 2h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 0 1zm-1 2h-1a.5.5 0 0 1 0-1h1a.5.5 0 0 1 0 1z" class="toolbar_text" fill="{{ toolbar_text|d }}"></path>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" x="650" y="65" class="toolbar_text" fill="{{ toolbar_text|d }}">
|
||||
<path d="M3 4h10a1 1 0 0 0 0-2H3a1 1 0 0 0 0 2zm10 3H3a1 1 0 0 0 0 2h10a1 1 0 0 0 0-2zm0 5H3a1 1 0 0 0 0 2h10a1 1 0 0 0 0-2z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" x="650" y="65" id="button-hamburger-svg">
|
||||
<path d="M3 4h10a1 1 0 0 0 0-2H3a1 1 0 0 0 0 2zm10 3H3a1 1 0 0 0 0 2h10a1 1 0 0 0 0-2zm0 5H3a1 1 0 0 0 0 2h10a1 1 0 0 0 0-2z" class="toolbar_text" fill="{{ toolbar_text|d }}"></path>
|
||||
</svg>
|
||||
<rect id="SvgRect1020" width="350" height="38" x="200" y="53"
|
||||
class="toolbar_field" fill="{{ toolbar_field|d('rgba(255,255,255,1)') }}" data-fill="rgba(255,255,255,1)"
|
||||
stroke="black" stroke-width="1" stroke-linejoin="round" stroke-opacity="0.5"></rect>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" x="210" y="63" class="toolbar_field_text" fill="{{ toolbar_field_text|d }}">
|
||||
<path d="M8 1a7 7 0 1 0 7 7 7.008 7.008 0 0 0-7-7zm0 13a6 6 0 1 1 6-6 6.007 6.007 0 0 1-6 6zm0-7a1 1 0 0 0-1 1v3a1 1 0 1 0 2 0V8a1 1 0 0 0-1-1zm0-3.188A1.188 1.188 0 1 0 9.188 5 1.188 1.188 0 0 0 8 3.812z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" x="210" y="63" id="icon-url-info-svg">
|
||||
<path d="M8 1a7 7 0 1 0 7 7 7.008 7.008 0 0 0-7-7zm0 13a6 6 0 1 1 6-6 6.007 6.007 0 0 1-6 6zm0-7a1 1 0 0 0-1 1v3a1 1 0 1 0 2 0V8a1 1 0 0 0-1-1zm0-3.188A1.188 1.188 0 1 0 9.188 5 1.188 1.188 0 0 0 8 3.812z" class="toolbar_field_text" fill="{{ toolbar_field_text|d }}" ></path>
|
||||
</svg>
|
||||
<text id="SvgText1021" x="230" y="77" class="toolbar_field_text" fill="{{ toolbar_field_text|d }}">
|
||||
<tspan id="SvgTspan1022">https://addons.mozilla.org/</tspan>
|
||||
</text>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="25" x="525" y="63" class="toolbar_field_text">
|
||||
<path d="M2 6a2 2 0 1 0 2 2 2 2 0 0 0-2-2zm6 0a2 2 0 1 0 2 2 2 2 0 0 0-2-2zm6 0a2 2 0 1 0 2 2 2 2 0 0 0-2-2z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="25" x="525" y="63" id="menu-url-more-svg">
|
||||
<path d="M2 6a2 2 0 1 0 2 2 2 2 0 0 0-2-2zm6 0a2 2 0 1 0 2 2 2 2 0 0 0-2-2zm6 0a2 2 0 1 0 2 2 2 2 0 0 0-2-2z" class="toolbar_field_text" fill="{{ toolbar_field_text|d }}" ></path>
|
||||
</svg>
|
||||
<line id="SvgText1023" x1="0" y1="99" x2="680" y2="99" stroke-width="1" stroke="black"/>
|
||||
<line id="SvgText1023" x1="0" y1="99" x2="{{ amo.THEME_PREVIEW_SIZE.width }}" y2="99" stroke-width="1" stroke="black"/>
|
||||
</svg>
|
||||
{% endautoescape %}
|
||||
|
|
|
@ -5,6 +5,7 @@ from datetime import datetime, timedelta
|
|||
|
||||
from django.conf import settings
|
||||
from django.core.files import temp
|
||||
from django.core.files.storage import default_storage as storage
|
||||
|
||||
import mock
|
||||
|
||||
|
@ -390,6 +391,10 @@ class TestAddonSubmitUpload(UploadTest, TestCase):
|
|||
all_ = sorted([f.filename for f in addon.current_version.all_files])
|
||||
assert all_ == [u'weta_fade-1.0.xpi'] # One XPI for all platforms.
|
||||
assert addon.type == amo.ADDON_STATICTHEME
|
||||
assert addon.previews.all().count() == 1
|
||||
preview = addon.previews.last()
|
||||
assert storage.exists(preview.image_path)
|
||||
assert preview.caption == unicode(addon.current_version.version)
|
||||
|
||||
@override_switch('allow-static-theme-uploads', active=True)
|
||||
def test_static_theme_submit_unlisted(self):
|
||||
|
@ -409,6 +414,8 @@ class TestAddonSubmitUpload(UploadTest, TestCase):
|
|||
all_ = sorted([f.filename for f in latest_version.all_files])
|
||||
assert all_ == [u'weta_fade-1.0.xpi'] # One XPI for all platforms.
|
||||
assert addon.type == amo.ADDON_STATICTHEME
|
||||
# Only listed submissions need a preview generated.
|
||||
assert addon.previews.all().count() == 0
|
||||
|
||||
@override_switch('allow-static-theme-uploads', active=True)
|
||||
def test_static_theme_wizard_listed(self):
|
||||
|
@ -437,6 +444,10 @@ class TestAddonSubmitUpload(UploadTest, TestCase):
|
|||
all_ = sorted([f.filename for f in addon.current_version.all_files])
|
||||
assert all_ == [u'weta_fade-1.0.xpi'] # One XPI for all platforms.
|
||||
assert addon.type == amo.ADDON_STATICTHEME
|
||||
assert addon.previews.all().count() == 1
|
||||
preview = addon.previews.last()
|
||||
assert storage.exists(preview.image_path)
|
||||
assert preview.caption == unicode(addon.current_version.version)
|
||||
|
||||
@override_switch('allow-static-theme-uploads', active=True)
|
||||
def test_static_theme_wizard_unlisted(self):
|
||||
|
@ -468,6 +479,8 @@ class TestAddonSubmitUpload(UploadTest, TestCase):
|
|||
all_ = sorted([f.filename for f in latest_version.all_files])
|
||||
assert all_ == [u'weta_fade-1.0.xpi'] # One XPI for all platforms.
|
||||
assert addon.type == amo.ADDON_STATICTHEME
|
||||
# Only listed submissions need a preview generated.
|
||||
assert addon.previews.all().count() == 0
|
||||
|
||||
|
||||
class DetailsPageMixin(object):
|
||||
|
@ -1361,6 +1374,13 @@ class VersionSubmitUploadMixin(object):
|
|||
self.assert3xx(response, self.get_next_url(version))
|
||||
log_items = ActivityLog.objects.for_addons(self.addon)
|
||||
assert log_items.filter(action=amo.LOG.ADD_VERSION.id)
|
||||
if self.channel == amo.RELEASE_CHANNEL_LISTED:
|
||||
assert self.addon.previews.all().count() == 1
|
||||
preview = self.addon.previews.last()
|
||||
assert storage.exists(preview.image_path)
|
||||
assert preview.caption == unicode(version)
|
||||
else:
|
||||
assert self.addon.previews.all().count() == 0
|
||||
|
||||
|
||||
class TestVersionSubmitUploadListed(VersionSubmitUploadMixin, UploadTest):
|
||||
|
|
|
@ -1380,9 +1380,9 @@ def _submit_upload(request, addon, channel, next_details, next_finish,
|
|||
platforms=data.get('supported_platforms', []),
|
||||
source=data['source'],
|
||||
channel=channel,
|
||||
parsed_data=data['parsed_data'])
|
||||
parsed_data=data['parsed_data'],
|
||||
user=request.user)
|
||||
version = addon.find_latest_version(channel=channel)
|
||||
AddonUser(addon=addon, user=request.user).save()
|
||||
url_args = [addon.slug]
|
||||
|
||||
check_validation_override(request, form, addon, version)
|
||||
|
|
|
@ -9,6 +9,7 @@ from datetime import timedelta
|
|||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
|
||||
import flufl.lock
|
||||
import lxml
|
||||
|
@ -448,6 +449,7 @@ class TestManifestJSONExtractor(TestCase):
|
|||
|
||||
class TestManifestJSONExtractorStaticTheme(TestManifestJSONExtractor):
|
||||
def parse(self, base_data):
|
||||
if 'theme' not in base_data.keys():
|
||||
base_data.update(theme={})
|
||||
return super(
|
||||
TestManifestJSONExtractorStaticTheme, self).parse(base_data)
|
||||
|
@ -535,6 +537,11 @@ class TestManifestJSONExtractorStaticTheme(TestManifestJSONExtractor):
|
|||
|
||||
assert exc.value.message.startswith('Cannot find min/max version.')
|
||||
|
||||
def test_theme_json_extracted(self):
|
||||
# Check theme data is extracted from the manifest and returned.
|
||||
data = {'theme': {'colors': {'textcolor': "#3deb60"}}}
|
||||
assert self.parse(data)['theme'] == data['theme']
|
||||
|
||||
|
||||
def test_zip_folder_content():
|
||||
extension_file = 'src/olympia/files/fixtures/files/extension.xpi'
|
||||
|
@ -898,3 +905,16 @@ class TestXMLVulnerabilities(TestCase):
|
|||
|
||||
# Setting it explicitly to `False` is fine too.
|
||||
lxml.etree.XMLParser(resolve_entities=False)
|
||||
|
||||
|
||||
def test_extract_header_img():
|
||||
file_obj = os.path.join(
|
||||
settings.ROOT, 'src/olympia/devhub/tests/addons/static_theme.zip')
|
||||
data = {'images': {'headerURL': 'weta.png'}}
|
||||
dest_path = tempfile.mkdtemp()
|
||||
header_file = dest_path + '/weta.png'
|
||||
assert not default_storage.exists(header_file)
|
||||
|
||||
utils.extract_header_img(file_obj, data, dest_path)
|
||||
assert default_storage.exists(header_file)
|
||||
assert default_storage.size(header_file) == 126447
|
||||
|
|
|
@ -498,6 +498,8 @@ class ManifestJSONExtractor(object):
|
|||
'permissions': self.get('permissions', []),
|
||||
'content_scripts': self.get('content_scripts', []),
|
||||
})
|
||||
elif self.type == amo.ADDON_STATICTHEME:
|
||||
data.update(theme=self.get('theme', {}))
|
||||
return data
|
||||
|
||||
|
||||
|
@ -1146,6 +1148,21 @@ def resolve_i18n_message(message, messages, locale, default_locale=None):
|
|||
return message['message']
|
||||
|
||||
|
||||
def extract_header_img(file_obj, theme_data, dest_path):
|
||||
"""Extract static theme header image from `file_obj`."""
|
||||
xpi = get_filepath(file_obj)
|
||||
images_dict = theme_data.get('images', {})
|
||||
# Get the reference in the manifest. theme_frame is the Chrome variant.
|
||||
header_url = images_dict.get(
|
||||
'headerURL', images_dict.get('theme_frame'))
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(xpi, 'r') as source:
|
||||
source.extract(header_url, dest_path)
|
||||
except IOError as ioerror:
|
||||
log.debug(ioerror)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def atomic_lock(lock_dir, lock_name, lifetime=60):
|
||||
"""A atomic, NFS safe implementation of a file lock.
|
||||
|
|
|
@ -78,6 +78,9 @@ CLEANCSS_BIN = 'cleancss'
|
|||
# Path to uglifyjs (our JS minifier).
|
||||
UGLIFY_BIN = 'uglifyjs' # Set as None to use YUI instead (at your risk).
|
||||
|
||||
# rsvg-convert is used to save our svg static theme previews to png
|
||||
RSVG_CONVERT_BIN = 'rsvg-convert'
|
||||
|
||||
FLIGTAR = 'amo-admins+fligtar-rip@mozilla.org'
|
||||
REVIEWERS_EMAIL = 'amo-editors@mozilla.org'
|
||||
THEMES_EMAIL = 'theme-reviews@mozilla.org'
|
||||
|
@ -1141,6 +1144,8 @@ CELERY_TASK_ROUTES = {
|
|||
'olympia.addons.tasks.save_theme_reupload': {'queue': 'priority'},
|
||||
'olympia.bandwagon.tasks.index_collections': {'queue': 'priority'},
|
||||
'olympia.bandwagon.tasks.unindex_collections': {'queue': 'priority'},
|
||||
'olympia.versions.tasks.generate_static_theme_preview': {
|
||||
'queue': 'priority'},
|
||||
|
||||
# Other queues we prioritize below.
|
||||
|
||||
|
|
|
@ -199,8 +199,8 @@ class VersionView(APIView):
|
|||
# channel will be ignored for new addons.
|
||||
if addon is None:
|
||||
channel = amo.RELEASE_CHANNEL_UNLISTED # New is always unlisted.
|
||||
addon = Addon.create_addon_from_upload_data(
|
||||
data=pkg, user=request.user, upload=filedata, channel=channel)
|
||||
addon = Addon.initialize_addon_from_upload(
|
||||
data=pkg, upload=filedata, channel=channel, user=request.user)
|
||||
created = True
|
||||
else:
|
||||
created = False
|
||||
|
|
|
@ -34,6 +34,7 @@ from olympia.translations.fields import (
|
|||
LinkifiedField, PurifiedField, TranslatedField, save_signal)
|
||||
|
||||
from .compare import version_dict, version_int
|
||||
from .tasks import generate_static_theme_preview
|
||||
|
||||
|
||||
log = olympia.core.logger.getLogger('z.versions')
|
||||
|
@ -206,6 +207,21 @@ class Version(OnChangeMixin, ModelBase):
|
|||
if send_signal:
|
||||
version_uploaded.send(sender=version)
|
||||
|
||||
# Generate a preview and icon for listed static themes
|
||||
if (addon.type == amo.ADDON_STATICTHEME and
|
||||
channel == amo.RELEASE_CHANNEL_LISTED):
|
||||
from olympia.addons.models import Preview # Avoid circular ref.
|
||||
dst_root = os.path.join(user_media_path('addons'), str(addon.id))
|
||||
theme_data = parsed_data.get('theme', {})
|
||||
version_root = os.path.join(dst_root, unicode(version.id))
|
||||
|
||||
utils.extract_header_img(
|
||||
version.all_files[0].file_path, theme_data, version_root)
|
||||
preview = Preview.objects.create(
|
||||
addon=addon, caption=unicode(version.version))
|
||||
generate_static_theme_preview.delay(
|
||||
theme_data, version_root, preview)
|
||||
|
||||
# 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)
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import os
|
||||
import StringIO
|
||||
import subprocess
|
||||
import tempfile
|
||||
from base64 import b64encode
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage as storage
|
||||
from django.template import loader
|
||||
|
||||
from PIL import Image
|
||||
|
||||
import olympia.core.logger
|
||||
from olympia import amo
|
||||
from olympia.amo.celery import task
|
||||
from olympia.amo.decorators import write
|
||||
|
||||
|
||||
log = olympia.core.logger.getLogger('z.files.utils')
|
||||
|
||||
|
||||
def write_svg_to_png(svg_content, out):
|
||||
tmp_args = {'dir': settings.TMP_PATH, 'mode': 'wb', 'suffix': '.svg'}
|
||||
with tempfile.NamedTemporaryFile(**tmp_args) as temporary_svg:
|
||||
temporary_svg.write(svg_content)
|
||||
temporary_svg.flush()
|
||||
|
||||
size = None
|
||||
try:
|
||||
if not os.path.exists(os.path.dirname(out)):
|
||||
os.makedirs(out)
|
||||
command = [
|
||||
settings.RSVG_CONVERT_BIN,
|
||||
'-o', out,
|
||||
temporary_svg.name
|
||||
]
|
||||
subprocess.check_call(command)
|
||||
size = amo.THEME_PREVIEW_SIZE
|
||||
except IOError as io_error:
|
||||
log.debug(io_error)
|
||||
except subprocess.CalledProcessError as process_error:
|
||||
log.debug(process_error)
|
||||
return size
|
||||
|
||||
|
||||
@task
|
||||
@write
|
||||
def generate_static_theme_preview(theme_manifest, header_root, preview):
|
||||
tmpl = loader.get_template(
|
||||
'devhub/addons/includes/static_theme_preview_svg.xml')
|
||||
context = {'amo': amo}
|
||||
context.update(theme_manifest.get('colors', {}))
|
||||
header_url = theme_manifest.get('images', {}).get('headerURL')
|
||||
|
||||
header_path = os.path.join(header_root, header_url)
|
||||
try:
|
||||
with storage.open(header_path, 'rb') as header_file:
|
||||
header_blob = header_file.read()
|
||||
with Image.open(StringIO.StringIO(header_blob)) as header_image:
|
||||
(width, height) = header_image.size
|
||||
context.update(header_src_height=height)
|
||||
meetOrSlice = ('meet' if width < amo.THEME_PREVIEW_SIZE.width
|
||||
else 'slice')
|
||||
context.update(
|
||||
preserve_aspect_ratio='xMaxYMin %s' % meetOrSlice)
|
||||
data_url = 'data:image/%s;base64,%s' % (
|
||||
header_image.format.lower(), b64encode(header_blob))
|
||||
context.update(header_src=data_url)
|
||||
except IOError as io_error:
|
||||
log.debug(io_error)
|
||||
|
||||
svg = tmpl.render(context).encode('utf-8')
|
||||
size = write_svg_to_png(svg, preview.image_path)
|
||||
if size:
|
||||
preview.update(sizes={'image': size})
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 42 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 124 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 60 KiB |
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
После Ширина: | Высота: | Размер: 170 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 131 KiB |
|
@ -0,0 +1,94 @@
|
|||
import math
|
||||
import os
|
||||
import tempfile
|
||||
from base64 import b64encode
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage as storage
|
||||
|
||||
import mock
|
||||
import pytest
|
||||
from PIL import Image, ImageChops
|
||||
|
||||
from olympia.addons.models import Preview
|
||||
from olympia.amo.tests import addon_factory
|
||||
from olympia.versions.tasks import (
|
||||
generate_static_theme_preview, write_svg_to_png)
|
||||
|
||||
|
||||
def test_write_svg_to_png():
|
||||
out = tempfile.mktemp()
|
||||
svg_xml = os.path.join(
|
||||
settings.ROOT,
|
||||
'src/olympia/versions/tests/static_themes/weta_theme.svg')
|
||||
svg_png = os.path.join(
|
||||
settings.ROOT,
|
||||
'src/olympia/versions/tests/static_themes/weta_theme.png')
|
||||
with storage.open(svg_xml, 'rb') as svgfile:
|
||||
svg = svgfile.read()
|
||||
write_svg_to_png(svg, out)
|
||||
assert storage.exists(out)
|
||||
# compare the image content. rms should be 0 but travis renders it
|
||||
# different... 19 is the magic difference.
|
||||
svg_png_img = Image.open(svg_png)
|
||||
svg_out_img = Image.open(out)
|
||||
image_diff = ImageChops.difference(svg_png_img, svg_out_img)
|
||||
sum_of_squares = sum(
|
||||
value * ((idx % 256) ** 2)
|
||||
for idx, value in enumerate(image_diff.histogram()))
|
||||
rms = math.sqrt(
|
||||
sum_of_squares / float(svg_png_img.size[0] * svg_png_img.size[1]))
|
||||
|
||||
assert rms < 19
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@mock.patch('olympia.versions.tasks.write_svg_to_png')
|
||||
@pytest.mark.parametrize(
|
||||
'header_url, header_height, preserve_aspect_ratio, mimetype', (
|
||||
('transparent.gif', 1, 'xMaxYMin meet', 'image/gif'),
|
||||
('weta.png', 200, 'xMaxYMin meet', 'image/png'),
|
||||
('wetalong.png', 200, 'xMaxYMin slice', 'image/png'),
|
||||
)
|
||||
)
|
||||
def test_generate_static_theme_preview(
|
||||
write_svg_to_png, header_url, header_height, preserve_aspect_ratio,
|
||||
mimetype):
|
||||
write_svg_to_png.return_value = (123, 456)
|
||||
theme_manifest = {
|
||||
"images": {
|
||||
"headerURL": header_url
|
||||
},
|
||||
"colors": {
|
||||
"accentcolor": "#918e43",
|
||||
"textcolor": "#3deb60",
|
||||
"toolbar_text": "#b5ba5b",
|
||||
"toolbar_field": "#cc29cc",
|
||||
"toolbar_field_text": "#17747d"
|
||||
}
|
||||
}
|
||||
header_root = os.path.join(
|
||||
settings.ROOT, 'src/olympia/versions/tests/static_themes/')
|
||||
addon = addon_factory()
|
||||
preview = Preview.objects.create(addon=addon)
|
||||
generate_static_theme_preview(theme_manifest, header_root, preview)
|
||||
write_svg_to_png.assert_called()
|
||||
((svg_content, png_path), _) = write_svg_to_png.call_args
|
||||
assert png_path == preview.image_path
|
||||
# check header is there.
|
||||
assert 'width="680" height="100" xmlns="http://www.w3.org/2000/' in (
|
||||
svg_content)
|
||||
# check image xml is correct
|
||||
image_tag = (
|
||||
'<image id="svg-header-img" width="680" height="%s" '
|
||||
'preserveAspectRatio="%s"' % (header_height, preserve_aspect_ratio))
|
||||
assert image_tag in svg_content, svg_content
|
||||
# and image content is included and was encoded
|
||||
with storage.open(header_root + header_url, 'rb') as header_file:
|
||||
header_blob = header_file.read()
|
||||
base_64_uri = 'data:%s;base64,%s' % (mimetype, b64encode(header_blob))
|
||||
assert 'xlink:href="%s"></image>' % base_64_uri in svg_content
|
||||
# check each of our colors above was included
|
||||
for (key, color) in theme_manifest['colors'].items():
|
||||
snippet = 'class="%s" fill="%s"' % (key, color)
|
||||
assert snippet in svg_content
|
|
@ -14,7 +14,7 @@ import requests
|
|||
import olympia.core.logger
|
||||
|
||||
from olympia import amo
|
||||
from olympia.addons.models import Addon, AddonCategory, AddonUser, Category
|
||||
from olympia.addons.models import Addon, AddonCategory, Category
|
||||
from olympia.amo.celery import task
|
||||
from olympia.amo.decorators import write
|
||||
from olympia.amo.templatetags.jinja_helpers import absolutify
|
||||
|
@ -220,8 +220,7 @@ def fetch_langpack(url, xpi, **kw):
|
|||
u'{name!r}.'.format(**data))
|
||||
|
||||
addon = Addon.from_upload(
|
||||
upload, [amo.PLATFORM_ALL.id], parsed_data=data)
|
||||
AddonUser(addon=addon, user=owner).save()
|
||||
upload, [amo.PLATFORM_ALL.id], parsed_data=data, user=owner)
|
||||
version = addon.versions.get()
|
||||
|
||||
if addon.default_locale.lower() == lang.lower():
|
||||
|
|
Загрузка…
Ссылка в новой задаче