Bug 614337 - Screen Shot Uploader
This commit is contained in:
Родитель
1953e89cb5
Коммит
d6dda89846
|
@ -180,7 +180,7 @@ class AddonFormMedia(AddonFormBase):
|
|||
fields = ('icon_upload', 'icon_type')
|
||||
|
||||
def save(self, addon, commit=True):
|
||||
if self.request.FILES:
|
||||
if 'icon_upload' in self.request.FILES:
|
||||
icon = self.request.FILES['icon_upload']
|
||||
icon.seek(0)
|
||||
dirname = addon.get_icon_dir()
|
||||
|
|
|
@ -1129,15 +1129,16 @@ class Preview(amo.models.ModelBase):
|
|||
self.image_url, ]
|
||||
return urls
|
||||
|
||||
def _image_url(self, thumb=True):
|
||||
def _image_url(self, url_template):
|
||||
if self.modified is not None:
|
||||
modified = int(time.mktime(self.modified.timetuple()))
|
||||
else:
|
||||
modified = 0
|
||||
url_template = (thumb and settings.PREVIEW_THUMBNAIL_URL or
|
||||
settings.PREVIEW_FULL_URL)
|
||||
return url_template % (self.id / 1000, self.id, modified)
|
||||
|
||||
def _image_path(self, url_template):
|
||||
return url_template % (self.id / 1000, self.id)
|
||||
|
||||
def as_dict(self, src=None):
|
||||
d = {'full': urlparams(self.image_url, src=src),
|
||||
'thumbnail': urlparams(self.thumbnail_url, src=src),
|
||||
|
@ -1146,11 +1147,19 @@ class Preview(amo.models.ModelBase):
|
|||
|
||||
@property
|
||||
def thumbnail_url(self):
|
||||
return self._image_url(thumb=True)
|
||||
return self._image_url(settings.PREVIEW_THUMBNAIL_URL)
|
||||
|
||||
@property
|
||||
def image_url(self):
|
||||
return self._image_url(thumb=False)
|
||||
return self._image_url(settings.PREVIEW_FULL_URL)
|
||||
|
||||
@property
|
||||
def thumbnail_path(self):
|
||||
return self._image_path(settings.PREVIEW_THUMBNAIL_PATH)
|
||||
|
||||
@property
|
||||
def image_path(self):
|
||||
return self._image_path(settings.PREVIEW_FULL_PATH)
|
||||
|
||||
|
||||
class AppSupport(amo.models.ModelBase):
|
||||
|
|
|
@ -206,6 +206,9 @@ MAX_CATEGORIES = 2
|
|||
# Icon upload sizes
|
||||
ADDON_ICON_SIZES = [32, 48, 64]
|
||||
|
||||
# Preview upload sizes [thumb, full]
|
||||
ADDON_PREVIEW_SIZES = [(200, 150), (700, 525)]
|
||||
|
||||
# These types don't maintain app compatibility in the db. Instead, we look at
|
||||
# APP.types and APP_TYPE_SUPPORT to figure out where they are compatible.
|
||||
NO_COMPAT = (ADDON_SEARCH, ADDON_PERSONA)
|
||||
|
|
|
@ -291,6 +291,11 @@ def resize_image(src, dst, size, remove_src=True):
|
|||
"""Resizes and image from src, to dst."""
|
||||
if src == dst:
|
||||
raise Exception("src and dst can't be the same: %s" % src)
|
||||
|
||||
dirname = os.path.dirname(dst)
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
|
||||
im = Image.open(src)
|
||||
im = im.convert('RGBA')
|
||||
im = processors.scale_and_crop(im, size)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import path
|
||||
import socket
|
||||
|
||||
from django import forms
|
||||
|
@ -12,7 +13,7 @@ from tower import ugettext as _, ugettext_lazy as _lazy
|
|||
import amo
|
||||
import addons.forms
|
||||
import paypal
|
||||
from addons.models import Addon, AddonUser, Charity
|
||||
from addons.models import Addon, AddonUser, Charity, Preview
|
||||
from amo.forms import AMOModelForm
|
||||
from applications.models import Application, AppVersion
|
||||
from files.models import File, FileUpload, Platform
|
||||
|
@ -22,6 +23,7 @@ from translations.fields import TransTextarea, TransField
|
|||
from translations.models import delete_translation
|
||||
from translations.forms import TranslationFormMixin
|
||||
from versions.models import License, Version, ApplicationsVersions
|
||||
from . import tasks
|
||||
|
||||
|
||||
class AuthorForm(happyforms.ModelForm):
|
||||
|
@ -454,3 +456,39 @@ class ReviewTypeForm(forms.Form):
|
|||
|
||||
class Step3Form(addons.forms.AddonFormBasic):
|
||||
description = TransField(widget=TransTextarea, required=False)
|
||||
|
||||
|
||||
class PreviewForm(happyforms.ModelForm):
|
||||
caption = TransField(widget=TransTextarea, required=False)
|
||||
file_upload = forms.FileField(required=False)
|
||||
upload_hash = forms.CharField(required=False)
|
||||
|
||||
def save(self, addon, commit=True):
|
||||
if self.cleaned_data:
|
||||
self.instance.addon = addon
|
||||
super(PreviewForm, self).save(commit=commit)
|
||||
|
||||
if self.cleaned_data['upload_hash']:
|
||||
upload_hash = self.cleaned_data['upload_hash']
|
||||
settings_path = settings.PREVIEWS_PATH
|
||||
upload_path = path.path(settings_path) / 'temp' / upload_hash
|
||||
|
||||
tasks.resize_preview.delay(str(upload_path),
|
||||
self.instance.thumbnail_path,
|
||||
self.instance.image_path)
|
||||
|
||||
class Meta:
|
||||
model = Preview
|
||||
fields = ('caption', 'file_upload', 'upload_hash', 'id')
|
||||
|
||||
|
||||
class BasePreviewFormSet(BaseModelFormSet):
|
||||
|
||||
def clean(self):
|
||||
if any(self.errors):
|
||||
return
|
||||
|
||||
|
||||
PreviewFormSet = modelformset_factory(Preview, formset=BasePreviewFormSet,
|
||||
form=PreviewForm, can_delete=True,
|
||||
extra=1)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
@ -8,6 +7,7 @@ from django.conf import settings
|
|||
from django.core.management import call_command
|
||||
from celeryutils import task
|
||||
|
||||
import amo
|
||||
from amo.decorators import write
|
||||
from amo.utils import resize_image
|
||||
from files.models import FileUpload, File, FileValidation
|
||||
|
@ -81,3 +81,21 @@ def resize_icon(src, dst, size, **kw):
|
|||
|
||||
except Exception, e:
|
||||
log.error("Error saving addon icon: %s" % e)
|
||||
|
||||
|
||||
@task(queue='images')
|
||||
def resize_preview(src, thumb_dst, full_dst, **kw):
|
||||
"""Resizes preview images."""
|
||||
log.info('[1@None] Resizing preview: %s' % thumb_dst)
|
||||
|
||||
try:
|
||||
# Generate the thumb.
|
||||
size = amo.ADDON_PREVIEW_SIZES[0]
|
||||
resize_image(src, thumb_dst, size, remove_src=False)
|
||||
|
||||
# Resize the original.
|
||||
size = amo.ADDON_PREVIEW_SIZES[1]
|
||||
resize_image(src, full_dst, size, remove_src=True)
|
||||
|
||||
except Exception, e:
|
||||
log.error("Error saving preview: %s" % e)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
{% block title %}{{ dev_page_title(_('Step 4'), addon) }}{% endblock %}
|
||||
|
||||
{% block primary %}
|
||||
<section class="addon-submission-process" id="submit-media" role="main">
|
||||
<h3>{{ _('Step 4. Add Images') }}</h3>
|
||||
<p>
|
||||
{% trans %}
|
||||
|
@ -54,6 +55,35 @@
|
|||
<li id="edit-icon-error"></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if preview_form %}
|
||||
{{ preview_form.management_form|safe }}
|
||||
{{ preview_form.non_form_errors()|safe }}
|
||||
<div id="file-list">
|
||||
{% for form in preview_form.forms %}
|
||||
<div class="preview">
|
||||
<div class="preview-thumb">
|
||||
{% if form.instance.id %}
|
||||
<img src="{{ form.instance.thumbnail_url }}">
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ form.id|safe }}
|
||||
<label>{{ _('Please provide a caption for this screen shot:') }}</label>
|
||||
{{ form.caption|safe }}
|
||||
<div class="preview_extra">
|
||||
{{ form.upload_hash|safe }}
|
||||
</div>
|
||||
{{ form.errors|safe }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="invisible-upload">
|
||||
<a class="button" href="#">{{ _('Add a screenshot...') }}</a>
|
||||
<input type="file" id="screenshot_upload" name="uploads" multiple
|
||||
data-upload-url="{{ url('devhub.addons.upload_preview', addon.slug) }}">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="submission-buttons addon-submission-field">
|
||||
<button type="submit">
|
||||
{{ _('Continue') }}
|
||||
|
|
|
@ -70,6 +70,48 @@
|
|||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<label>{{ _("Screenshots and videos") }}</label>
|
||||
</th>
|
||||
<td class="edit-previews-readonly">
|
||||
{% if editable %}
|
||||
{% if preview_form: %}
|
||||
{{ preview_form.management_form|safe }}
|
||||
{{ preview_form.non_form_errors()|safe }}
|
||||
<div id="file-list">
|
||||
{% for form in preview_form.forms %}
|
||||
<div class="preview">
|
||||
<div class="preview-thumb">
|
||||
{% if form.instance.id %}
|
||||
<img src="{{ form.instance.thumbnail_url }}">
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ form.id|safe }}
|
||||
<label>{{ _('Please provide a caption for this screen shot:') }}</label>
|
||||
{{ form.caption|safe }}
|
||||
<div class="preview_extra">
|
||||
{{ form.upload_hash|safe }}
|
||||
</div>
|
||||
{{ form.errors|safe }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="invisible-upload">
|
||||
<a class="button" href="#">{{ _('Add a screenshot...') }}</a>
|
||||
<input type="file" id="screenshot_upload" name="uploads" multiple
|
||||
data-upload-url="{{ url('devhub.addons.upload_preview', addon.slug) }}">
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% for preview in addon.previews.all() %}
|
||||
<div class="preview-thumb">
|
||||
<img src="{{ preview.thumbnail_url }}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import path
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
|
@ -64,7 +65,7 @@ def _uploader(resize_size, final_size):
|
|||
|
||||
if isinstance(final_size, list):
|
||||
for rsize, fsize in zip(resize_size, final_size):
|
||||
dest_name = '1234'
|
||||
dest_name = str(path.path(settings.ADDON_ICONS_PATH) / '1234')
|
||||
|
||||
resize_icon(src.name, dest_name, resize_size)
|
||||
dest_image = Image.open("%s-%s.png" % (dest_name, rsize))
|
||||
|
|
|
@ -6,7 +6,6 @@ import shutil
|
|||
import socket
|
||||
import tempfile
|
||||
from decimal import Decimal
|
||||
import shutil
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
|
@ -71,7 +70,7 @@ class HubTest(test_utils.TestCase):
|
|||
|
||||
def clone_addon(self, num, addon_id=57132):
|
||||
ids = []
|
||||
for i in xrange(num):
|
||||
for i in range(num):
|
||||
addon = Addon.objects.get(id=addon_id)
|
||||
addon.id = addon.guid = None
|
||||
addon.save()
|
||||
|
@ -271,8 +270,9 @@ def formset(*args, **kw):
|
|||
prefix and initial_count can be set in **kw.
|
||||
"""
|
||||
prefix = kw.pop('prefix', 'form')
|
||||
total_count = kw.pop('total_count', len(args))
|
||||
initial_count = kw.pop('initial_count', len(args))
|
||||
data = {prefix + '-TOTAL_FORMS': len(args),
|
||||
data = {prefix + '-TOTAL_FORMS': total_count,
|
||||
prefix + '-INITIAL_FORMS': initial_count}
|
||||
for idx, d in enumerate(args):
|
||||
data.update(('%s-%s-%s' % (prefix, idx, k), v)
|
||||
|
@ -949,6 +949,20 @@ class TestEdit(test_utils.TestCase):
|
|||
|
||||
self.addon = self.get_addon()
|
||||
|
||||
def formset_new_form(self, *args, **kw):
|
||||
ctx = self.client.get(self.get_url('media', True)).context
|
||||
|
||||
blank = initial(ctx['preview_form'].forms[-1])
|
||||
blank.update(**kw)
|
||||
return blank
|
||||
|
||||
def formset_media(self, *args, **kw):
|
||||
kw.setdefault('initial_count', 0)
|
||||
kw.setdefault('prefix', 'files')
|
||||
|
||||
fs = formset(*[a for a in args] + [self.formset_new_form()], **kw)
|
||||
return dict([(k, '' if v is None else v) for k, v in fs.items()])
|
||||
|
||||
def tearDown(self):
|
||||
reset_redis(self._redis)
|
||||
|
||||
|
@ -1396,8 +1410,9 @@ class TestEdit(test_utils.TestCase):
|
|||
|
||||
def test_edit_media_defaulticon(self):
|
||||
data = dict(icon_type='')
|
||||
data_formset = self.formset_media(**data)
|
||||
|
||||
r = self.client.post(self.get_url('media', True), data)
|
||||
r = self.client.post(self.get_url('media', True), data_formset)
|
||||
eq_(r.context['form'].errors, {})
|
||||
addon = self.get_addon()
|
||||
|
||||
|
@ -1408,8 +1423,9 @@ class TestEdit(test_utils.TestCase):
|
|||
|
||||
def test_edit_media_preuploadedicon(self):
|
||||
data = dict(icon_type='icon/appearance')
|
||||
data_formset = self.formset_media(**data)
|
||||
|
||||
r = self.client.post(self.get_url('media', True), data)
|
||||
r = self.client.post(self.get_url('media', True), data_formset)
|
||||
eq_(r.context['form'].errors, {})
|
||||
addon = self.get_addon()
|
||||
|
||||
|
@ -1424,8 +1440,9 @@ class TestEdit(test_utils.TestCase):
|
|||
|
||||
data = dict(icon_type='image/png',
|
||||
icon_upload=src_image)
|
||||
data_formset = self.formset_media(**data)
|
||||
|
||||
r = self.client.post(self.get_url('media', True), data)
|
||||
r = self.client.post(self.get_url('media', True), data_formset)
|
||||
eq_(r.context['form'].errors, {})
|
||||
addon = self.get_addon()
|
||||
|
||||
|
@ -1454,8 +1471,9 @@ class TestEdit(test_utils.TestCase):
|
|||
|
||||
data = dict(icon_type='image/png',
|
||||
icon_upload=src_image)
|
||||
data_formset = self.formset_media(**data)
|
||||
|
||||
r = self.client.post(self.get_url('media', True), data)
|
||||
r = self.client.post(self.get_url('media', True), data_formset)
|
||||
eq_(r.context['form'].errors, {})
|
||||
addon = self.get_addon()
|
||||
|
||||
|
@ -1478,8 +1496,9 @@ class TestEdit(test_utils.TestCase):
|
|||
|
||||
data = dict(icon_type='image/png',
|
||||
icon_upload=src_image)
|
||||
data_formset = self.formset_media(**data)
|
||||
|
||||
r = self.client.post(self.get_url('media', True), data)
|
||||
r = self.client.post(self.get_url('media', True), data_formset)
|
||||
error = 'Icons must be either PNG or JPG.'
|
||||
self.assertFormError(r, 'form', 'icon_upload', error)
|
||||
|
||||
|
@ -1506,10 +1525,65 @@ class TestEdit(test_utils.TestCase):
|
|||
def test_icon_animated(self):
|
||||
filehandle = open(get_image_path('animated.png'), 'rb')
|
||||
data = {'icon_type': 'image/png', 'icon_upload': filehandle}
|
||||
res = self.client.post(self.get_url('media', True), data)
|
||||
data_formset = self.formset_media(**data)
|
||||
res = self.client.post(self.get_url('media', True), data_formset)
|
||||
eq_(res.context['form'].errors['icon_upload'][0],
|
||||
u'Icons cannot be animated.')
|
||||
|
||||
def preview_add(self, amount=1):
|
||||
img = "%s/img/amo2009/tab-mozilla.png" % settings.MEDIA_ROOT
|
||||
src_image = open(img, 'rb')
|
||||
|
||||
data = dict(upload_preview=src_image)
|
||||
data_formset = self.formset_media(**data)
|
||||
url = reverse('devhub.addons.upload_preview', args=['a3615'])
|
||||
r = self.client.post(url, data_formset)
|
||||
|
||||
details = json.loads(r.content)
|
||||
upload_hash = details['upload_hash']
|
||||
|
||||
# Create and post with the formset.
|
||||
fields = []
|
||||
for i in range(amount):
|
||||
fields.append(self.formset_new_form(caption='hi',
|
||||
upload_hash=upload_hash))
|
||||
data_formset = self.formset_media(*fields)
|
||||
|
||||
self.get_url('media', True)
|
||||
|
||||
r = self.client.post(self.get_url('media', True), data_formset)
|
||||
|
||||
def test_edit_media_preview_add(self):
|
||||
self.preview_add()
|
||||
|
||||
eq_(str(self.get_addon().previews.all()[0].caption), 'hi')
|
||||
|
||||
def test_edit_media_preview_edit(self):
|
||||
self.preview_add()
|
||||
preview = self.get_addon().previews.all()[0]
|
||||
edited = {'caption': 'bye',
|
||||
'upload_hash': '',
|
||||
'id': preview.id,
|
||||
'file_upload': None}
|
||||
|
||||
data_formset = self.formset_media(edited, initial_count=1)
|
||||
|
||||
self.client.post(self.get_url('media', True), data_formset)
|
||||
|
||||
eq_(str(self.get_addon().previews.all()[0].caption), 'bye')
|
||||
eq_(len(self.get_addon().previews.all()), 1)
|
||||
|
||||
def test_edit_media_preview_add_another(self):
|
||||
self.preview_add()
|
||||
self.preview_add()
|
||||
|
||||
eq_(len(self.get_addon().previews.all()), 2)
|
||||
|
||||
def test_edit_media_preview_add_two(self):
|
||||
self.preview_add(2)
|
||||
|
||||
eq_(len(self.get_addon().previews.all()), 2)
|
||||
|
||||
def test_log(self):
|
||||
data = {'developer_comments': 'This is a test'}
|
||||
o = ActivityLog.objects
|
||||
|
@ -2566,13 +2640,30 @@ class TestSubmitStep4(TestSubmitBase):
|
|||
|
||||
def test_post(self):
|
||||
data = dict(icon_type='')
|
||||
r = self.client.post(self.url, data)
|
||||
data_formset = self.formset_media(**data)
|
||||
r = self.client.post(self.url, data_formset)
|
||||
eq_(r.status_code, 302)
|
||||
eq_(self.get_step().step, 5)
|
||||
|
||||
def formset_new_form(self, *args, **kw):
|
||||
ctx = self.client.get(self.url).context
|
||||
|
||||
blank = initial(ctx['preview_form'].forms[-1])
|
||||
blank.update(**kw)
|
||||
return blank
|
||||
|
||||
def formset_media(self, *args, **kw):
|
||||
kw.setdefault('initial_count', 0)
|
||||
kw.setdefault('prefix', 'files')
|
||||
|
||||
fs = formset(*[a for a in args] + [self.formset_new_form()], **kw)
|
||||
return dict([(k, '' if v is None else v) for k, v in fs.items()])
|
||||
|
||||
def test_edit_media_defaulticon(self):
|
||||
data = dict(icon_type='')
|
||||
self.client.post(self.url, data)
|
||||
data_formset = self.formset_media(**data)
|
||||
|
||||
self.client.post(self.url, data_formset)
|
||||
|
||||
addon = self.get_addon()
|
||||
|
||||
|
@ -2584,7 +2675,8 @@ class TestSubmitStep4(TestSubmitBase):
|
|||
|
||||
def test_edit_media_preuploadedicon(self):
|
||||
data = dict(icon_type='icon/appearance')
|
||||
self.client.post(self.url, data)
|
||||
data_formset = self.formset_media(**data)
|
||||
self.client.post(self.url, data_formset)
|
||||
|
||||
addon = self.get_addon()
|
||||
|
||||
|
@ -2600,10 +2692,10 @@ class TestSubmitStep4(TestSubmitBase):
|
|||
|
||||
data = dict(icon_type='image/png',
|
||||
icon_upload=src_image)
|
||||
data_formset = self.formset_media(**data)
|
||||
self.client.post(self.url, data_formset)
|
||||
|
||||
self.client.post(self.url, data)
|
||||
addon = self.get_addon()
|
||||
|
||||
eq_('/'.join(addon.get_icon_url(64).split('/')[-3:-1]),
|
||||
'addon_icon/%s' % addon.id)
|
||||
|
||||
|
@ -2624,8 +2716,9 @@ class TestSubmitStep4(TestSubmitBase):
|
|||
|
||||
data = dict(icon_type='image/png',
|
||||
icon_upload=src_image)
|
||||
data_formset = self.formset_media(**data)
|
||||
|
||||
self.client.post(self.url, data)
|
||||
self.client.post(self.url, data_formset)
|
||||
addon = self.get_addon()
|
||||
|
||||
eq_('/'.join(addon.get_icon_url(64).split('/')[-3:-1]),
|
||||
|
@ -2645,21 +2738,25 @@ class TestSubmitStep4(TestSubmitBase):
|
|||
def test_client_lied(self):
|
||||
filehandle = open(get_image_path('non-animated.gif'), 'rb')
|
||||
data = {'icon_type': 'image/png', 'icon_upload': filehandle}
|
||||
res = self.client.post(self.url, data)
|
||||
data_formset = self.formset_media(**data)
|
||||
|
||||
res = self.client.post(self.url, data_formset)
|
||||
eq_(res.context['form'].errors['icon_upload'][0],
|
||||
u'Icons must be either PNG or JPG.')
|
||||
|
||||
def test_icon_animated(self):
|
||||
filehandle = open(get_image_path('animated.png'), 'rb')
|
||||
data = {'icon_type': 'image/png', 'icon_upload': filehandle}
|
||||
res = self.client.post(self.url, data)
|
||||
data_formset = self.formset_media(**data)
|
||||
res = self.client.post(self.url, data_formset)
|
||||
eq_(res.context['form'].errors['icon_upload'][0],
|
||||
u'Icons cannot be animated.')
|
||||
|
||||
def test_icon_non_animated(self):
|
||||
filehandle = open(get_image_path('non-animated.png'), 'rb')
|
||||
data = {'icon_type': 'image/png', 'icon_upload': filehandle}
|
||||
res = self.client.post(self.url, data)
|
||||
data_formset = self.formset_media(**data)
|
||||
res = self.client.post(self.url, data_formset)
|
||||
eq_(res.status_code, 302)
|
||||
eq_(self.get_step().step, 5)
|
||||
|
||||
|
|
|
@ -39,6 +39,9 @@ detail_patterns = patterns('',
|
|||
url('^edit_(?P<section>[^/]+)(?:/(?P<editable>[^/]+))?$',
|
||||
views.addons_section, name='devhub.addons.section'),
|
||||
|
||||
url('^upload_preview$', views.upload_preview,
|
||||
name='devhub.addons.upload_preview'),
|
||||
|
||||
url('^versions/$', views.version_list, name='devhub.versions'),
|
||||
url('^versions/delete$', views.version_delete,
|
||||
name='devhub.versions.delete'),
|
||||
|
|
|
@ -3,12 +3,14 @@ import collections
|
|||
import functools
|
||||
import json
|
||||
import os
|
||||
import path
|
||||
import sys
|
||||
import traceback
|
||||
import uuid
|
||||
|
||||
from django import http
|
||||
from django.db.models import Count
|
||||
from django.conf import settings
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils.http import urlquote
|
||||
from django.views.decorators.cache import never_cache
|
||||
|
@ -574,12 +576,22 @@ def addons_section(request, addon_id, addon, section, editable=False):
|
|||
if section not in models:
|
||||
return http.HttpResponseNotFound()
|
||||
|
||||
previews = []
|
||||
if section == 'media':
|
||||
previews = forms.PreviewFormSet(request.POST or None,
|
||||
prefix='files', queryset=addon.previews.all())
|
||||
|
||||
if editable:
|
||||
if request.method == 'POST':
|
||||
form = models[section](request.POST, request.FILES,
|
||||
instance=addon, request=request)
|
||||
if form.is_valid():
|
||||
if form.is_valid() and (not previews or previews.is_valid()):
|
||||
addon = form.save(addon)
|
||||
|
||||
if previews:
|
||||
for preview in previews.forms:
|
||||
preview.save(addon)
|
||||
|
||||
editable = False
|
||||
if section == 'media':
|
||||
amo.log(amo.LOG.CHANGE_ICON, addon)
|
||||
|
@ -601,7 +613,8 @@ def addons_section(request, addon_id, addon, section, editable=False):
|
|||
'form': form,
|
||||
'editable': editable,
|
||||
'categories': categories,
|
||||
'tags': tags}
|
||||
'tags': tags,
|
||||
'preview_form': previews}
|
||||
|
||||
return jingo.render(request,
|
||||
'devhub/includes/addon_edit_%s.html' % section, data)
|
||||
|
@ -615,6 +628,27 @@ def icon_status(request, addon_id, addon):
|
|||
return os.path.exists(destination)
|
||||
|
||||
|
||||
@json_view
|
||||
@dev_required
|
||||
def upload_preview(request, addon_id, addon):
|
||||
if 'upload_preview' in request.FILES:
|
||||
upload_preview = request.FILES['upload_preview']
|
||||
upload_preview.seek(0)
|
||||
|
||||
upload_hash = uuid.uuid4().hex
|
||||
loc = path.path(settings.PREVIEWS_PATH) / 'temp' / upload_hash
|
||||
if not loc.dirname().exists():
|
||||
loc.dirname().makedirs()
|
||||
|
||||
with open(loc, 'wb') as fd:
|
||||
for chunk in upload_preview:
|
||||
fd.write(chunk)
|
||||
|
||||
return {'upload_hash': upload_hash, 'errors': False}
|
||||
|
||||
return {'errors': [_('There was an error uploading your preview.')]}
|
||||
|
||||
|
||||
@dev_required
|
||||
def version_edit(request, addon_id, addon, version_id):
|
||||
version = get_object_or_404(Version, pk=version_id, addon=addon)
|
||||
|
@ -812,16 +846,24 @@ def submit_describe(request, addon_id, addon, step):
|
|||
@dev_required
|
||||
@submit_step(4)
|
||||
def submit_media(request, addon_id, addon, step):
|
||||
form = addon_forms.AddonFormMedia(request.POST or None, request.FILES,
|
||||
instance=addon, request=request)
|
||||
form_icon = addon_forms.AddonFormMedia(request.POST or None,
|
||||
request.FILES, instance=addon, request=request)
|
||||
form_previews = forms.PreviewFormSet(request.POST or None,
|
||||
prefix='files', queryset=addon.previews.all())
|
||||
|
||||
if (request.method == 'POST' and
|
||||
form_icon.is_valid() and form_previews.is_valid()):
|
||||
addon = form_icon.save(addon)
|
||||
|
||||
for preview in form_previews.forms:
|
||||
preview.save(addon)
|
||||
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
addon = form.save(addon)
|
||||
SubmitStep.objects.filter(addon=addon).update(step=5)
|
||||
return redirect('devhub.submit.5', addon.slug)
|
||||
|
||||
return jingo.render(request, 'devhub/addons/submit/media.html',
|
||||
{'form': form, 'addon': addon, 'step': step})
|
||||
{'form': form_icon, 'addon': addon, 'step': step,
|
||||
'preview_form': form_previews})
|
||||
|
||||
|
||||
@dev_required
|
||||
|
|
|
@ -274,6 +274,53 @@ Bug 622030- TODO (potch) fix this later
|
|||
width: 16px;
|
||||
}
|
||||
|
||||
#file-list .preview {
|
||||
overflow: auto;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px dotted #ADD0DC;
|
||||
}
|
||||
|
||||
#file-list div:last-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#file-list .preview .preview_extra {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#file-list .preview label {
|
||||
color: #666666;
|
||||
display: block;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#file-list .preview textarea {
|
||||
font-size: 0.9em;
|
||||
height: 34px;
|
||||
line-height: 1.1em;
|
||||
width: 375px;
|
||||
}
|
||||
|
||||
.preview-thumb {
|
||||
border: 3px solid #D2EDF5;
|
||||
float: left;
|
||||
height: 94px;
|
||||
margin-right: 15px;
|
||||
width: 125px;
|
||||
}
|
||||
|
||||
.preview-thumb img {
|
||||
max-width: 125px;
|
||||
}
|
||||
|
||||
.edit-previews-readonly .preview-thumb {
|
||||
margin-bottom: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#icons_default {
|
||||
border: 1px solid #6A89AC;
|
||||
max-height: 86px;
|
||||
|
|
|
@ -47,6 +47,7 @@ $(document).ready(function() {
|
|||
// Submission > Media
|
||||
if($('#submit-media').length) {
|
||||
initUploadIcon();
|
||||
initUploadPreview();
|
||||
}
|
||||
|
||||
if ($(".version-upload").length) {
|
||||
|
@ -210,16 +211,120 @@ function initEditAddon() {
|
|||
|
||||
hideSameSizedIcons();
|
||||
initUploadIcon();
|
||||
initUploadPreview();
|
||||
}
|
||||
|
||||
function create_new_preview_field() {
|
||||
var forms_count = $('#id_files-TOTAL_FORMS').val(),
|
||||
last = $('#file-list .preview').last(),
|
||||
last_clone = last.clone();
|
||||
|
||||
$('input, textarea, div', last_clone).each(function(){
|
||||
var re = new RegExp(format("-{0}-", [forms_count-1])),
|
||||
new_count = "-"+forms_count+"-",
|
||||
el = $(this);
|
||||
|
||||
$.each(['id','name','data-name'], function(k,v){
|
||||
if(el.attr(v)) {
|
||||
el.attr(v, el.attr(v).replace(re, new_count));
|
||||
}
|
||||
});
|
||||
});
|
||||
$(last).after(last_clone);
|
||||
$('#id_files-TOTAL_FORMS').val(parseInt(forms_count) + 1);
|
||||
|
||||
return last;
|
||||
}
|
||||
|
||||
function initUploadPreview() {
|
||||
$('#edit-addon-media, #submit-media').delegate('#screenshot_upload', 'change', function(e){
|
||||
url = $(this).attr('data-upload-url');
|
||||
|
||||
// TODO(gkoberger): Make sure this works on non-Fx browsers that don't
|
||||
//support multiple files
|
||||
$.each($('#screenshot_upload')[0].files, function(k, f){
|
||||
|
||||
var data = f.getAsBinary(),
|
||||
form = create_new_preview_field(),
|
||||
file = {},
|
||||
xhr = new XMLHttpRequest(),
|
||||
output = "",
|
||||
boundary = "BoUnDaRyStRiNg";
|
||||
|
||||
file.name = f.name || f.fileName;
|
||||
file.size = f.size;
|
||||
file.data = '';
|
||||
file.aborted = false;
|
||||
|
||||
xhr.open("POST", url, true);
|
||||
|
||||
xhr.setRequestHeader("Content-Length", file.size);
|
||||
xhr.setRequestHeader('Content-Disposition', 'file; name="upload";');
|
||||
xhr.setRequestHeader("X-File-Name", file.name);
|
||||
xhr.setRequestHeader("X-File-Size", file.size);
|
||||
|
||||
xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
|
||||
xhr.setRequestHeader('Content-length', false);
|
||||
xhr.setRequestHeader("Content-Type", "multipart/form-data;" +
|
||||
"boundary=" + boundary);
|
||||
|
||||
output += "--" + boundary + "\r\n";
|
||||
output += "Content-Disposition: form-data; name=\"csrfmiddlewaretoken\";";
|
||||
|
||||
parent_form = $('#screenshot_upload').closest('form')
|
||||
output += "\r\n\r\n";
|
||||
output += parent_form.find('input[name=csrfmiddlewaretoken]').val();
|
||||
output += "\r\n";
|
||||
|
||||
output += "--" + boundary + "\r\n";
|
||||
output += "Content-Disposition: form-data; name=\"upload_preview\";";
|
||||
|
||||
output += " filename=\"new-upload\";\r\n";
|
||||
output += "Content-Type: " + f.type;
|
||||
|
||||
output += "\r\n\r\n";
|
||||
output += data;
|
||||
output += "\r\n";
|
||||
output += "--" + boundary + "--";
|
||||
|
||||
xhr.onreadystatechange = function(){
|
||||
if (xhr.readyState == 4 && xhr.responseText &&
|
||||
(xhr.status == 200 || xhr.status == 304)) {
|
||||
try {
|
||||
json = JSON.parse(xhr.responseText);
|
||||
} catch(err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
form.find('[name$=upload_hash]').val(json.upload_hash);
|
||||
}
|
||||
}
|
||||
xhr.sendAsBinary(output);
|
||||
});
|
||||
|
||||
$('#screenshot_upload').val("");
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function initUploadIcon() {
|
||||
$('#edit-addon-media').delegate('form', 'submit', function(e) {
|
||||
e.preventDefault();
|
||||
multipartUpload($(this), function(e, xhr){
|
||||
if (xhr.readyState == 4 && xhr.responseText &&
|
||||
(xhr.status == 200 || xhr.status == 304)) {
|
||||
$('#edit-addon-media').html(xhr.responseText);
|
||||
|
||||
hideSameSizedIcons();
|
||||
}
|
||||
});
|
||||
|
||||
if($('input[name=icon_type]:checked').val().match(/^image\//)) {
|
||||
setTimeout(checkIconStatus, 1000);
|
||||
}
|
||||
});
|
||||
$('#edit-addon-media').delegate('form', 'submit', multipartUpload);
|
||||
|
||||
$('#edit-addon-media, #submit-media').delegate('#icons_default a', 'click', function(e){
|
||||
e.preventDefault();
|
||||
|
||||
|
@ -907,60 +1012,59 @@ function checkIconStatus() {
|
|||
);
|
||||
}
|
||||
|
||||
function multipartUpload(e) {
|
||||
e.preventDefault();
|
||||
|
||||
function multipartUpload(form, onreadystatechange) {
|
||||
var xhr = new XMLHttpRequest(),
|
||||
boundary = "BoUnDaRyStRiNg";
|
||||
boundary = "BoUnDaRyStRiNg",
|
||||
form = $(form),
|
||||
serialized = form.serializeArray(),
|
||||
submit_items = [],
|
||||
output = "";
|
||||
|
||||
xhr.open("POST", $(this).attr('action'), true)
|
||||
xhr.open("POST", form.attr('action'), true)
|
||||
xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
|
||||
xhr.setRequestHeader('Content-length', false);
|
||||
xhr.setRequestHeader("Content-Type", "multipart/form-data;" +
|
||||
"boundary=" + boundary);
|
||||
|
||||
// Sorry this is so ugly.
|
||||
content = [
|
||||
"Content-Type: multipart/form-data; boundary=" + boundary,
|
||||
"",
|
||||
"--" + boundary,
|
||||
"Content-Disposition: form-data; name=\"icon_type\"",
|
||||
"",
|
||||
$('input[name="icon_type"]:checked', $('#icons_default')).val(),
|
||||
$('input[type=file]', form).each(function(){
|
||||
var files = $(this)[0].files,
|
||||
file_field = $(this);
|
||||
|
||||
"--" + boundary,
|
||||
"Content-Disposition: form-data; name=\"csrfmiddlewaretoken\"",
|
||||
"",
|
||||
$('input[name="csrfmiddlewaretoken"]', $('#edit-addon-media')).val()];
|
||||
$.each(files, function(k, file) {
|
||||
var data = file.getAsBinary();
|
||||
|
||||
if($('input[name=icon_type]:checked').val().match(/^image\//)) {
|
||||
// There's a file to be uploaded.
|
||||
serialized.push({
|
||||
'name': $(file_field).attr('name'),
|
||||
'value': data,
|
||||
'file_type': file.type,
|
||||
'file_name': file.name || file.fileName
|
||||
});
|
||||
});
|
||||
|
||||
var file = $('#id_icon_upload')[0].files[0],
|
||||
data = file.getAsBinary();
|
||||
});
|
||||
|
||||
image = [
|
||||
"--" + boundary,
|
||||
"Content-Disposition: form-data; name=\"icon_upload\";" +
|
||||
"filename=\"new-icon\"",
|
||||
"Content-Type: " + file.type,
|
||||
"",
|
||||
data,
|
||||
"--" + boundary + "--"];
|
||||
$.each(serialized, function(k, v){
|
||||
output += "--" + boundary + "\r\n";
|
||||
output += "Content-Disposition: form-data; name=\"" + v.name + "\";";
|
||||
|
||||
content = $.merge(content, image);
|
||||
if(v.file_name != undefined) {
|
||||
output += " filename=\"new-upload\";\r\n";
|
||||
output += "Content-Type: " + v.file_type;
|
||||
}
|
||||
|
||||
output += "\r\n\r\n";
|
||||
output += v.value;
|
||||
output += "\r\n";
|
||||
|
||||
});
|
||||
|
||||
output += "--" + boundary + "--";
|
||||
|
||||
if(onreadystatechange) {
|
||||
xhr.onreadystatechange = function(e){ onreadystatechange(e, xhr); }
|
||||
}
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4 && xhr.responseText &&
|
||||
(xhr.status == 200 || xhr.status == 304)) {
|
||||
$('#edit-addon-media').html(xhr.responseText);
|
||||
|
||||
hideSameSizedIcons();
|
||||
}
|
||||
};
|
||||
|
||||
xhr.sendAsBinary(content.join('\r\n'));
|
||||
xhr.sendAsBinary(output);
|
||||
}
|
||||
|
||||
function hideSameSizedIcons() {
|
||||
|
|
|
@ -462,6 +462,9 @@ PREVIEWS_PATH = UPLOADS_PATH + '/previews'
|
|||
USERPICS_PATH = UPLOADS_PATH + '/userpics'
|
||||
ADDON_ICONS_DEFAULT_PATH = os.path.join(MEDIA_ROOT, 'img/addon-icons')
|
||||
|
||||
PREVIEW_THUMBNAIL_PATH = (PREVIEWS_PATH + '/thumbs/%s/%d.png')
|
||||
PREVIEW_FULL_PATH = (PREVIEWS_PATH + '/full/%s/%d.png')
|
||||
|
||||
# URL paths
|
||||
# paths for images, e.g. mozcdn.com/amo or '/static'
|
||||
STATIC_URL = SITE_URL
|
||||
|
|
Загрузка…
Ссылка в новой задаче