Skip to content

Commit c5be892

Browse files
committed
Merge commit from fork
* Raise (and display) error when icon or preview upload hash is invalid * Add a couple comments explaining where the hash is coming from
1 parent 2d5da98 commit c5be892

File tree

4 files changed

+61
-20
lines changed

4 files changed

+61
-20
lines changed

src/olympia/devhub/forms.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from django import forms
99
from django.conf import settings
10-
from django.core.validators import MinLengthValidator
10+
from django.core.validators import MinLengthValidator, RegexValidator
1111
from django.db.models import Q
1212
from django.forms.models import BaseModelFormSet, modelformset_factory
1313
from django.forms.widgets import RadioSelect
@@ -238,7 +238,15 @@ class AddonFormMedia(AddonFormBase):
238238
icon_type = forms.CharField(
239239
widget=IconTypeSelect(choices=ICON_TYPES), required=False
240240
)
241-
icon_upload_hash = forms.CharField(required=False)
241+
icon_upload_hash = forms.CharField(
242+
required=False,
243+
validators=[
244+
# Should look like an uuid, it's generated by upload_image()
245+
RegexValidator(
246+
r'^[a-f0-9]{32}$', _('There was an error uploading your image.')
247+
)
248+
],
249+
)
242250

243251
class Meta:
244252
model = Addon
@@ -1344,7 +1352,15 @@ class DescribeFormUnlistedContentOptimization(
13441352
class PreviewForm(TranslationFormMixin, AMOModelForm):
13451353
caption = TransField(widget=TransTextarea, required=False)
13461354
file_upload = forms.FileField(required=False)
1347-
upload_hash = forms.CharField(required=False)
1355+
upload_hash = forms.CharField(
1356+
required=False,
1357+
validators=[
1358+
# Should look like an uuid, it's generated by upload_image()
1359+
RegexValidator(
1360+
r'^[a-f0-9]{32}$', _('There was an error uploading your image.')
1361+
)
1362+
],
1363+
)
13481364

13491365
def save(self, addon, commit=True):
13501366
if self.cleaned_data:

src/olympia/devhub/templates/devhub/addons/forms_shared/media.html

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
</label>
2929
{% endif %}
3030
{% if editable %}
31+
{{ main_form.icon_upload_hash.errors }}
3132
<div id="icon_preview">
3233
<div class="icon_preview" id="icon_preview_32">
3334
<img src="{{ addon.get_icon_url(32) }}" alt="">
@@ -76,9 +77,6 @@
7677
{% endif %}
7778
{{ _('Icons will be resized to 128x128 pixels if larger.') }}
7879
</div>
79-
<ul class="errorlist">
80-
<li id="edit-icon-error"></li>
81-
</ul>
8280
{% else %}
8381
<div id="icon_preview_readonly">
8482
<img src="{{ addon.get_icon_url(128) }}" alt="">
@@ -110,6 +108,7 @@
110108
{{ preview_form.non_form_errors() }}
111109
<div id="file-list">
112110
{% for form in preview_form.forms %}
111+
{{ form.errors }}
113112
<div class="preview">
114113
<span class="handle">&nbsp;</span>
115114
{% if form.instance.id %}
@@ -133,7 +132,6 @@
133132
<div class="preview_extra">
134133
{{ form.upload_hash }}
135134
</div>
136-
{{ form.errors }}
137135
</div>
138136
{% endfor %}
139137
</div>

src/olympia/devhub/tests/test_forms.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import shutil
33
import tempfile
4+
import uuid
45
from datetime import datetime, timedelta
56
from unittest import mock
67

@@ -531,19 +532,28 @@ def setUp(self):
531532
def test_preview_modified(self, update_mock):
532533
addon = Addon.objects.get(pk=3615)
533534
name = 'transparent.png'
535+
upload_hash = uuid.uuid4().hex
534536
form = forms.PreviewForm(
535-
{'caption': 'test', 'upload_hash': name, 'position': 1}
537+
{'caption': 'test', 'upload_hash': upload_hash, 'position': 1}
536538
)
537-
with storage.open(os.path.join(self.dest, name), 'wb') as f:
539+
with storage.open(os.path.join(self.dest, upload_hash), 'wb') as f:
538540
shutil.copyfileobj(open(get_image_path(name), 'rb'), f)
539541
assert form.is_valid()
540542
form.save(addon)
541543
assert update_mock.called
542544

545+
def test_preview_invalid_hash(self):
546+
form = forms.PreviewForm(
547+
{'caption': 'test', 'upload_hash': 'something.png', 'position': 1}
548+
)
549+
assert not form.is_valid()
550+
assert form.errors == {
551+
'upload_hash': ['There was an error uploading your image.']
552+
}
553+
543554
def test_caption_too_long(self):
544-
name = 'transparent.png'
545555
form = forms.PreviewForm(
546-
{'caption': 'û' * 281, 'upload_hash': name, 'position': 1}
556+
{'caption': 'û' * 281, 'upload_hash': uuid.uuid4().hex, 'position': 1}
547557
)
548558
assert form.fields['caption'].max_length == 280
549559
assert form.fields['caption'].widget.attrs['maxlength'] == '280'
@@ -555,11 +565,11 @@ def test_caption_too_long(self):
555565
def test_preview_transparency(self):
556566
addon = Addon.objects.get(pk=3615)
557567
name = 'transparent-cotton'
558-
hash = '12345678abcd'
568+
upload_hash = uuid.uuid4().hex
559569
form = forms.PreviewForm(
560-
{'caption': 'test', 'upload_hash': hash, 'position': 1}
570+
{'caption': 'test', 'upload_hash': upload_hash, 'position': 1}
561571
)
562-
with storage.open(os.path.join(self.dest, hash), 'wb') as f:
572+
with storage.open(os.path.join(self.dest, upload_hash), 'wb') as f:
563573
shutil.copyfileobj(open(get_image_path(name + '.png'), 'rb'), f)
564574
assert form.is_valid()
565575
form.save(addon)
@@ -575,10 +585,11 @@ def test_preview_transparency(self):
575585
def test_preview_size(self, pngcrush_image_mock):
576586
addon = Addon.objects.get(pk=3615)
577587
name = 'teamaddons.jpg'
588+
upload_hash = uuid.uuid4().hex
578589
form = forms.PreviewForm(
579-
{'caption': 'test', 'upload_hash': name, 'position': 1}
590+
{'caption': 'test', 'upload_hash': upload_hash, 'position': 1}
580591
)
581-
with storage.open(os.path.join(self.dest, name), 'wb') as f:
592+
with storage.open(os.path.join(self.dest, upload_hash), 'wb') as f:
582593
shutil.copyfileobj(open(get_image_path(name), 'rb'), f)
583594
assert form.is_valid()
584595
form.save(addon)
@@ -1236,17 +1247,30 @@ def test_default_icons(self):
12361247
@mock.patch('olympia.amo.models.ModelBase.update')
12371248
def test_icon_modified(self, update_mock):
12381249
name = 'transparent.png'
1250+
icon_upload_hash = uuid.uuid4().hex
12391251
form = forms.AddonFormMedia(
1240-
{'icon_upload_hash': name}, request=self.request, instance=self.addon
1252+
{'icon_upload_hash': icon_upload_hash},
1253+
request=self.request,
1254+
instance=self.addon,
12411255
)
1242-
1243-
dest = os.path.join(self.icon_path, name)
1256+
dest = os.path.join(self.icon_path, icon_upload_hash)
12441257
with storage.open(dest, 'wb') as f:
12451258
shutil.copyfileobj(open(get_image_path(name), 'rb'), f)
12461259
assert form.is_valid()
12471260
form.save(addon=self.addon)
12481261
assert update_mock.called
12491262

1263+
def test_icon_invalid_hash(self):
1264+
form = forms.AddonFormMedia(
1265+
{'icon_upload_hash': 'some-icon.png'},
1266+
request=self.request,
1267+
instance=self.addon,
1268+
)
1269+
assert not form.is_valid()
1270+
assert form.errors == {
1271+
'icon_upload_hash': ['There was an error uploading your image.']
1272+
}
1273+
12501274

12511275
class TestCategoryForm(TestCase):
12521276
def test_only_one_possible_category_for_dicts(self):

static/js/zamboni/devhub.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ function addonFormSubmit() {
287287
$document.scrollTop($document.height() - scrollBottom);
288288
truncateFields();
289289
annotateLocalizedErrors(parent_div);
290-
if (parent_div.is('#edit-addon-media')) {
290+
if (!hasErrors && parent_div.is('#edit-addon-media')) {
291291
imageStatus.start();
292292
hideSameSizedIcons();
293293
}
@@ -1201,6 +1201,9 @@ let imageStatus = {
12011201
},
12021202
newurl: function (orig) {
12031203
let bst = new Date().getTime();
1204+
if (!orig) {
1205+
orig = '';
1206+
}
12041207
orig += (orig.indexOf('?') > 1 ? '&' : '?') + bst;
12051208
return orig;
12061209
},

0 commit comments

Comments
 (0)