diff --git a/media/css/devreg/submit.less b/media/css/devreg/submit.less index 9d76ca8a2a..d1a591c1dc 100644 --- a/media/css/devreg/submit.less +++ b/media/css/devreg/submit.less @@ -93,3 +93,47 @@ h2 .soon { } } } + +#submit-payment-type { + .wrapper { + display: inline-block; + width: 222px; + margin-right: 10px; + &:last-child { + margin-right: 0; + width: 221px; + } + a { + text-decoration: none; + text-align: center; + padding: 180px 10px 20px; + margin-bottom: 0; + &#free-os, &#paid-os { + background: url(../../img/developers/firefox_phone.png) no-repeat center 25px; + } + &#free-desktop { + background: url(../../img/developers/firefox_logo.png) no-repeat center 25px; + } + &#free-phone { + background: url(../../img/developers/firefox_android_phone.png) no-repeat center 25px; + } + &#free-tablet { + background: url(../../img/developers/firefox_android_tablet.png) no-repeat center 40px; + } + &#free-os, &#free-desktop, &#free-phone, &#free-tablet, &#paid-os { + &.selected { + background-color: @notice-yellow; + } + } + div { + color: #484848; + } + } + } +} + +#upload-file { + &.spacer { + margin-top: 60px; + } +} diff --git a/media/css/devreg/tabs.less b/media/css/devreg/tabs.less new file mode 100644 index 0000000000..2cffd6c073 --- /dev/null +++ b/media/css/devreg/tabs.less @@ -0,0 +1,51 @@ +@import 'lib'; + +.tabbable { + position: relative; + margin-top: 52px; + .border-radius(0 0 5px 5px); + hgroup { + position: absolute; + top: -44px; + width: 100%; + left: -1px; + h2 { + .border-box(); + display: inline-block; + a { + .border-box(); + display: block; + text-align: center; + border: 1px solid #e5e2db; + .border-radius(5px 5px 0 0); + line-height: 42px; + margin-right: 10px; + background-color: #f9f9f9; + border-bottom: 0; + color: #949494; + &:hover { + text-decoration: none; + } + } + &.active a { + border: 1px solid @taupe; + border-bottom: 1px solid @white; + background-color: @white; + color: #484848; + } + &:last-child { + position: absolute; + right: -2px; + a { + margin-right: 0; + } + } + } + } + .tab { + display: none; + &.active { + display: block; + } + } +} diff --git a/media/img/developers/firefox_android_phone.png b/media/img/developers/firefox_android_phone.png new file mode 100644 index 0000000000..02580afe6d Binary files /dev/null and b/media/img/developers/firefox_android_phone.png differ diff --git a/media/img/developers/firefox_android_tablet.png b/media/img/developers/firefox_android_tablet.png new file mode 100644 index 0000000000..604004388a Binary files /dev/null and b/media/img/developers/firefox_android_tablet.png differ diff --git a/media/img/developers/firefox_logo.png b/media/img/developers/firefox_logo.png new file mode 100644 index 0000000000..091bea3b94 Binary files /dev/null and b/media/img/developers/firefox_logo.png differ diff --git a/media/img/developers/firefox_phone.png b/media/img/developers/firefox_phone.png new file mode 100644 index 0000000000..f2daf7cefd Binary files /dev/null and b/media/img/developers/firefox_phone.png differ diff --git a/media/js/common/upload-packaged-app.js b/media/js/common/upload-packaged-app.js index c15662a4c8..2be5b3906f 100644 --- a/media/js/common/upload-packaged-app.js +++ b/media/js/common/upload-packaged-app.js @@ -212,6 +212,8 @@ $upload_field.trigger('upload_success_results', [file, results]); }}; + $('#id_upload').val(results.upload); + $('#id_packaged').val(true); upload_progress_inside.animate({'width': '100%'}, animateArgs); }); @@ -303,8 +305,10 @@ upload_progress_outside.attr('class', 'bar-success'); upload_progress_inside.fadeOut(); - $upload_field.trigger('reenable_uploader'); + $('footer.listing-footer').removeClass('hidden'); + $('button.upload-file-submit').removeAttr('disabled').focus(); + $upload_field.trigger('reenable_uploader'); upload_results.addClass('status-pass'); $('').text(message).appendTo(upload_results); diff --git a/media/js/devreg/devhub.js b/media/js/devreg/devhub.js index b73ec49fea..34e36c0592 100644 --- a/media/js/devreg/devhub.js +++ b/media/js/devreg/devhub.js @@ -180,6 +180,7 @@ $(document).ready(function() { function check_webapp_validation(results) { var $upload_field = $('#upload-webapp-url'); $('#id_upload').val(results.upload); + $('#id_packaged').val(''); if(results.error) { $upload_field.trigger("upload_finished", [false, results, results.error]); } else if(! results.validation) { diff --git a/media/js/devreg/submit.js b/media/js/devreg/submit.js index 029f22e084..ff13bbe1aa 100644 --- a/media/js/devreg/submit.js +++ b/media/js/devreg/submit.js @@ -1,6 +1,14 @@ (function(exports) { "use strict"; + function _pd(func) { + // Prevent-default function wrapper. + return function(e) { + e.preventDefault(); + func.apply(this, arguments); + }; + } + exports.houdini = function() { // Initialize magic labels. $(document).delegate('.houdini.ready .edit', 'click', _pd(function(e) { @@ -34,9 +42,63 @@ $ctx.addClass('active'); }); }); - }) + }); + }; + + // Reset selected device buttons and values. + $('#submit-payment-type h2 a').click(function(e) { + $('#submit-payment-type a.choice').removeClass('selected'); + $('#id_free').val([]); + $('#id_paid').val([]); + }); + + + // When a big device button is clicked, update the form. + $('#submit-payment-type a.choice').on('click', + function(event) { + var $this = $(this), + $input = $('#id_' + this.id.split('-')[0]), + old = $input.val() || [], + val = $this.data('value'); + + if (old.indexOf(val) === -1) { + $this.addClass('selected'); + old.push(val); + $input.val(old); + } else { + $this.removeClass('selected'); + delete old[old.indexOf(val)]; + $input.val(old); + } + show_packaged(); + event.preventDefault(); + } + ); + + // Show packaged. + function show_packaged() { + var $target = $('#upload-file hgroup h2'); + if ($target.length < 2) { + return; + } + if ($('#id_free option[value=free-os]:selected').length || + $('#id_paid option[value=paid-os]:selected').length) { + $target.eq(1).css({'display': 'inline'}); + } else { + $target.eq(1).css({'display': 'none'}); + } } + // On page load, update the big device buttons with the values in the form. + $('#upload-webapp select').each(function(i, e) { + $.each($(e).val() || [], function() { + $('#submit-payment-type #' + this).addClass('selected'); + }); + }); + + // Hide the packaged tab, if needed. + show_packaged(); + })(typeof exports === 'undefined' ? (this.submit_details = {}) : exports); diff --git a/media/js/devreg/tabs.js b/media/js/devreg/tabs.js new file mode 100644 index 0000000000..57da09bc37 --- /dev/null +++ b/media/js/devreg/tabs.js @@ -0,0 +1,37 @@ +(function() { + // Tabbable + $('.tabbable').each(function() { + var $this = $(this); + $this.find('.active h2').addClass('active'); + + var $headers = $this.find('.tab h2').detach(), + numTabs = $headers.length; + + if (numTabs === (0 || 1)) { + return; + } + + var w = Math.floor(100 / numTabs), + $hgroup = $('
'); + + $headers.css({'width': w + '%'}); + $hgroup.append($headers); + $this.prepend($hgroup); + + $hgroup.find('a').each(function(i, e) { + $(this).on('click.switchtab', function(evt) { + var $myParent = $(evt.target).parent(); + if ($myParent.hasClass('active')) { + evt.preventDefault(); + return; + } else { + $hgroup.find('h2').removeClass('active'); + $myParent.addClass('active'); + } + $this.find('.tab').removeClass('active'); + $this.find('.tab:eq(' + i + ')').addClass('active'); + evt.preventDefault(); + }); + }); + }); +})(); diff --git a/migrations/498-b2g-payments-submission.sql b/migrations/498-b2g-payments-submission.sql new file mode 100644 index 0000000000..6e567a91b3 --- /dev/null +++ b/migrations/498-b2g-payments-submission.sql @@ -0,0 +1,3 @@ +INSERT INTO `waffle_switch_mkt` (name, active, note) + VALUES ('allow-b2g-paid-submission', 0, + 'Enable this to allow paid apps in the submission process.'); diff --git a/mkt/asset_bundles.py b/mkt/asset_bundles.py index 1f0792d66a..0e60b0222c 100644 --- a/mkt/asset_bundles.py +++ b/mkt/asset_bundles.py @@ -58,6 +58,7 @@ CSS = { 'css/devreg/submit-details.less', 'css/devreg/validation.less', 'css/devreg/submit.less', + 'css/devreg/tabs.less', 'css/impala/personas.less', 'css/impala/colorpicker.less', @@ -206,6 +207,7 @@ JS = { # New stuff. 'js/devreg/devhub.js', 'js/devreg/submit.js', + 'js/devreg/tabs.js', 'js/devreg/edit.js', 'js/impala/persona_creation.js', 'js/lib/jquery.minicolors.js', diff --git a/mkt/submit/forms.py b/mkt/submit/forms.py index 42148ce218..0a7dfa718d 100644 --- a/mkt/submit/forms.py +++ b/mkt/submit/forms.py @@ -4,6 +4,7 @@ from django import forms import happyforms from tower import ugettext as _, ugettext_lazy as _lazy +import waffle from addons.forms import AddonFormBasic from addons.models import Addon, AddonUpsell @@ -38,31 +39,125 @@ class DevAgreementForm(happyforms.Form): notification_id=app_surveys.id, update={'enabled': True}) + + + class NewWebappForm(happyforms.Form): + # The selections for free. + FREE = ( + ('free-os', _lazy('Firefox OS')), + ('free-desktop', _lazy('Firefox')), + ('free-phone', _lazy('Firefox Mobile')), + ('free-tablet', _lazy('Firefox Tablet')), + ) + + # The selections for paid. + PAID = ( + ('paid-os', _lazy('Firefox OS')), + ) + + # Extra information about those values for display in the page. + DEVICE_LOOKUP = { + 'free-os': _lazy('Fully open mobile ecosystem'), + 'free-desktop': _lazy('Windows, Mac and Linux'), + 'free-phone': _lazy('Android smartphones'), + 'free-tablet': _lazy('Android tablets'), + 'paid-os': _lazy('Fully open mobile ecosystem'), + } + + ERRORS = {'both': _lazy(u'Cannot be free and paid.'), + 'none': _lazy(u'Please select a device.'), + 'packaged': _lazy(u'Packaged apps are only valid ' + u'for Firefox OS.')} + upload = forms.ModelChoiceField(widget=forms.HiddenInput, queryset=FileUpload.objects.filter(valid=True), error_messages={'invalid_choice': _lazy(u'There was an error with your' - ' upload. Please try again.')}) + u' upload. Please try' + u' again.')}) - def __init__(self, *args, **kw): - self.addon = kw.pop('addon', None) - self.is_packaged = kw.pop('is_packaged', False) - super(NewWebappForm, self).__init__(*args, **kw) + packaged = forms.BooleanField(required=False) + free = forms.MultipleChoiceField(choices=FREE, required=False) + paid = forms.MultipleChoiceField(choices=PAID, required=False) + + + def _add_error(self, msg): + self._errors['free'] = self._errors['paid'] = self.ERRORS[msg] + + + def _get_combined(self): + return set(self.cleaned_data.get('free', []) + + self.cleaned_data.get('paid', [])) + + def clean(self): + data = self.cleaned_data + + # Check that they didn't select both. + if data.get('free') and data.get('paid'): + self._add_error('both') + return + + # Check that they selected one. + if not data.get('free') and not data.get('paid'): + self._add_error('none') + self._errors['free'] = self._errors['paid'] = self.ERRORS['none'] + return + + # Packaged apps are only valid for firefox os. + if self.is_packaged(): + if not set(self._get_combined()).issubset(['paid-os', 'free-os']): + self._add_error('packaged') + return + + # Now run the packaged app check, done in clean, because + # clean_packaged needs to be processed first. + try: + pkg = parse_addon(data['upload'], self.addon) + except forms.ValidationError, e: + self._errors['upload'] = self.error_class(e.messages) + return - def clean_upload(self): - upload = self.cleaned_data['upload'] - if self.is_packaged: - pkg = parse_addon(upload, self.addon) ver = pkg.get('version') if (ver and self.addon and self.addon.versions.filter(version=ver).exists()): - raise forms.ValidationError( - _(u'Version %s already exists') % ver) + self._errors['upload'] = _(u'Version %s already exists') % ver + return else: # Throw an error if this is a dupe. # (JS sets manifest as `upload.name`.) - verify_app_domain(upload.name) - return upload + try: + verify_app_domain(data['upload'].name) + except forms.ValidationError, e: + self._errors['upload'] = self.error_class(e.messages) + return + + return data + + def get_devices(self): + """Returns a device based on the requested free or paid.""" + platforms = {'os': amo.DEVICE_MOBILE, + 'desktop': amo.DEVICE_DESKTOP, + 'phone': amo.DEVICE_MOBILE, + 'tablet': amo.DEVICE_TABLET} + return [platforms[t.split('-', 1)[1]] for t in self._get_combined()] + + def get_paid(self): + """Returns the premium type.""" + if self.cleaned_data.get('paid', False): + return amo.ADDON_PREMIUM + return amo.ADDON_FREE + + def is_packaged(self): + return self.cleaned_data.get('packaged', False) + + def __init__(self, *args, **kw): + self.addon = kw.pop('addon', None) + super(NewWebappForm, self).__init__(*args, **kw) + if not waffle.switch_is_active('allow-b2g-paid-submission'): + del self.fields['paid'] + + if not waffle.switch_is_active('allow-packaged-app-uploads'): + del self.fields['packaged'] class PaypalSetupForm(happyforms.Form): @@ -87,13 +182,6 @@ class PaypalSetupForm(happyforms.Form): return data -class PremiumTypeForm(happyforms.Form): - premium_type = forms.TypedChoiceField(coerce=lambda x: int(x), - choices=amo.ADDON_PREMIUM_TYPES.items(), - widget=forms.RadioSelect(), - label=_lazy(u'Will your app use payments?')) - - class UpsellForm(happyforms.Form): price = forms.ModelChoiceField(queryset=Price.objects.active(), label=_lazy(u'App Price'), diff --git a/mkt/submit/templates/submit/choose.html b/mkt/submit/templates/submit/choose.html deleted file mode 100644 index 66f7d003e9..0000000000 --- a/mkt/submit/templates/submit/choose.html +++ /dev/null @@ -1,73 +0,0 @@ -{% extends 'developers/base_impala.html' %} - -{% set hosted_url = 'https://developer.mozilla.org/en-US/docs/Apps/Manifest' %} -{# TODO(bug 7884448): Change this link when that bug gets closed. #} -{% set packaged_url = 'https://bugzilla.mozilla.org/show_bug.cgi?id=784448' %} - -{% set title = _('Submit an App') %} - -{% block title %}{{ hub_page_title(title) }}{% endblock %} - -{% block content %} - {{ hub_breadcrumbs(items=[(None, _('Submit App'))]) }} -
-

{{ _('Submit an App') }}

- {{ progress(request, addon=None, step=step) }} -
-
-

{{ _('Choose an App Type') }}

-
-
-

- {% trans url=url('submit.app.manifest') %} - If the app is hosted on your own server, - then your app type is
- Hosted - {% endtrans %} -

-

- {% trans %} - What's next: submit your app manifest URL - {% endtrans %} -

-

- {% trans %} - Learn more about app manifests on MDN. - {% endtrans %} -

- {# TODO(bug 784448): Remove when other a.soon is gone. #} -   -
-
-

- {% trans url=url('submit.app.package') %} - If the app is going to be hosted by Mozilla, - then your app type is
- Packaged - {% endtrans %} -

-

- {% trans %} - What's next: upload your packaged app - {% endtrans %} -

-

- {% trans %} - Learn more about packaged apps on MDN. - {% endtrans %} -

- Coming soon -
-
-
- - -
-{% endblock %} diff --git a/mkt/submit/templates/submit/manifest.html b/mkt/submit/templates/submit/manifest.html index 712edba0cd..fee6b50753 100644 --- a/mkt/submit/templates/submit/manifest.html +++ b/mkt/submit/templates/submit/manifest.html @@ -6,24 +6,56 @@ {% block title %}{{ hub_page_title(title) }}{% endblock %} +{% macro button(form, item) %} + +{% endmacro %} + {% block content %} {{ hub_breadcrumbs(items=[(None, _('Submit App'))]) }}

{{ _('Submit an App') }}

{{ progress(request, addon=None, step=step) }}
-
-

{{ _("Where's Your Manifest?") }}

-

- {% trans %} - Kick off things by creating your app's manifest and entering its URL - below. Learn about manifests. - {% endtrans %} -

-
+ + +
+
+ {% if waffle.switch('allow-b2g-paid-submission') %} +

{{ _('Free') }}

+ {% else %} +

{{ _('Device Type') }}

+ {% endif %} +
{{ form.errors.free }}
+ {% for item in form.fields['free'].choices %} + {{ button(form, item) }} + {% endfor %} +
+ + {% if waffle.switch('allow-b2g-paid-submission') %} + + {% endif %} +
+ +
+
+ {% if waffle.switch('allow-packaged-app-uploads') %} +

Hosted

+ {% endif %} +

{{ _("Submit your app manifest URL") }}

- {# TODO: Feel free to add more content, links to tips, sample manifests, builders. Please do. Really. #} -

- {% trans %} - Learn more about app manifests on MDN. - {% endtrans %} -

+ + +
+ {% if waffle.switch('allow-packaged-app-uploads') %} +

Packaged

+ + {% endif %} +
+ +
+ {{ csrf() }} + + +
+ {% trans %} + What's next: fill out the rest of your app's details + {% endtrans %} + +
+
+ +

+ {% trans %}Learn more about app manifests on MDN.{% endtrans %} + +

+ {% endblock %} diff --git a/mkt/submit/templates/submit/upload.html b/mkt/submit/templates/submit/upload.html deleted file mode 100644 index df618430f0..0000000000 --- a/mkt/submit/templates/submit/upload.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends 'developers/base_impala.html' %} - -{% set title = _('Upload Your App') %} -{% block title %}{{ hub_page_title(title) }}{% endblock %} - -{% block content %} - {{ hub_breadcrumbs(items=[(None, title)]) }} -
-

{{ title }}

- {{ progress(request, addon=None, step=step) }} -
-
-

{{ _("Where's Your Packaged App?") }}

-
- {{ csrf() }} -

- {% trans %} - Use the fields below to upload your packaged app. After upload, a - series of automated validation tests will be run on your file. - {% endtrans %} -

-
- - - {{ form.non_field_errors() }} - {{ form.upload.errors }} -
- -
-
-
-
-{% endblock %} diff --git a/mkt/submit/tests/test_forms.py b/mkt/submit/tests/test_forms.py new file mode 100644 index 0000000000..5df03ad576 --- /dev/null +++ b/mkt/submit/tests/test_forms.py @@ -0,0 +1,100 @@ +import mock +from nose.tools import eq_ + +import amo +import amo.tests +from files.models import FileUpload +from mkt.submit import forms + + +class TestNewWebappForm(amo.tests.TestCase): + + def setUp(self): + self.file = FileUpload.objects.create(valid=True) + + def test_not_free_or_paid(self): + form = forms.NewWebappForm({}) + assert not form.is_valid() + eq_(form.ERRORS['none'], form.errors['free']) + eq_(form.ERRORS['none'], form.errors['paid']) + + def test_not_paid(self): + form = forms.NewWebappForm({'paid': ['paid-os']}) + assert not form.is_valid() + eq_(form.ERRORS['none'], form.errors['free']) + eq_(form.ERRORS['none'], form.errors['paid']) + + def test_paid(self): + self.create_switch('allow-b2g-paid-submission') + form = forms.NewWebappForm({'paid': ['paid-os'], + 'upload': self.file.uuid}) + assert form.is_valid() + eq_(form.get_paid(), amo.ADDON_PREMIUM) + + def test_free(self): + self.create_switch('allow-b2g-paid-submission') + form = forms.NewWebappForm({'free': ['free-os'], + 'upload': self.file.uuid}) + assert form.is_valid() + eq_(form.get_paid(), amo.ADDON_FREE) + + def test_platform(self): + self.create_switch('allow-b2g-paid-submission') + for data, res in ( + ({'free': ['free-os']}, [amo.DEVICE_MOBILE]), + ({'paid': ['paid-os']}, [amo.DEVICE_MOBILE]), + ({'free': ['free-os', + 'free-phone']}, [amo.DEVICE_MOBILE]), + ({'free': ['free-phone', + 'free-tablet']}, + [amo.DEVICE_MOBILE, amo.DEVICE_TABLET]), + ): + data['upload'] = self.file.uuid + form = forms.NewWebappForm(data) + assert form.is_valid(), form.errors + assert set(res) == set(form.get_devices()) + + def test_both(self): + self.create_switch('allow-b2g-paid-submission') + form = forms.NewWebappForm({'paid': ['paid-os'], + 'free': ['free-os']}) + assert not form.is_valid() + eq_(form.ERRORS['both'], form.errors['free']) + eq_(form.ERRORS['both'], form.errors['paid']) + + def test_multiple(self): + form = forms.NewWebappForm({'free': ['free-os', + 'free-desktop'], + 'upload': self.file.uuid}) + assert form.is_valid() + + def test_not_packaged(self): + form = forms.NewWebappForm({'free': ['free-os'], + 'upload': self.file.uuid, + 'packaged': True}) + assert form.is_valid(), form.errors + assert not form.is_packaged() + + def test_not_packaged_allowed(self): + self.create_switch('allow-packaged-app-uploads') + form = forms.NewWebappForm({'free': ['free-os'], + 'upload': self.file.uuid}) + assert form.is_valid(), form.errors + assert not form.is_packaged() + + @mock.patch('mkt.submit.forms.parse_addon') + def test_packaged_allowed(self, parse_addon): + self.create_switch('allow-packaged-app-uploads') + form = forms.NewWebappForm({'free': ['free-os'], + 'upload': self.file.uuid, + 'packaged': True}) + assert form.is_valid() + assert form.is_packaged() + + def test_packaged_wrong_device(self): + self.create_switch('allow-packaged-app-uploads') + form = forms.NewWebappForm({'free': ['free-desktop'], + 'upload': self.file.uuid, + 'packaged': True}) + assert not form.is_valid(), form.errors + eq_(form.ERRORS['packaged'], form.errors['paid']) diff --git a/mkt/submit/tests/test_views.py b/mkt/submit/tests/test_views.py index ef8b9d012f..3c45b76688 100644 --- a/mkt/submit/tests/test_views.py +++ b/mkt/submit/tests/test_views.py @@ -124,20 +124,16 @@ class TestTerms(TestSubmit): self._test_progress_display([], 'terms') def test_agree(self): - self.create_switch(name='allow-packaged-app-uploads') - r = self.client.post(self.url, {'read_dev_agreement': True}) - self.assert3xx(r, reverse('submit.app.choose')) + self.client.post(self.url, {'read_dev_agreement': True}) dt = self.get_user().read_dev_agreement assert close_to_now(dt), ( 'Expected date of agreement read to be close to now. Was %s' % dt) eq_(UserNotification.objects.count(), 0) def test_agree_and_sign_me_up(self): - self.create_switch(name='allow-packaged-app-uploads') - r = self.client.post(self.url, {'read_dev_agreement': - datetime.datetime.now(), - 'newsletter': True}) - self.assert3xx(r, reverse('submit.app.choose')) + self.client.post(self.url, {'read_dev_agreement': + datetime.datetime.now(), + 'newsletter': True}) dt = self.get_user().read_dev_agreement assert close_to_now(dt), ( 'Expected date of agreement read to be close to now. Was %s' % dt) @@ -190,16 +186,12 @@ class TestManifest(TestSubmit): # So jump me to the Manifest step. r = self.client.get(reverse('submit.app'), follow=True) self.assert3xx(r, reverse('submit.app.manifest')) - # Now with waffles! - self.create_switch(name='allow-packaged-app-uploads') - r = self.client.get(reverse('submit.app'), follow=True) - self.assert3xx(r, reverse('submit.app.choose')) def test_page(self): self._step() r = self.client.get(self.url) eq_(r.status_code, 200) - eq_(pq(r.content)('#submit-manifest').length, 1) + eq_(pq(r.content)('#upload-file').length, 1) def test_progress_display(self): self._step() @@ -208,12 +200,11 @@ class TestManifest(TestSubmit): class UploadAddon(object): - def post(self, desktop_platforms=[amo.PLATFORM_ALL], mobile_platforms=[], - expect_errors=False): - d = dict(upload=self.upload.pk, - desktop_platforms=[p.id for p in desktop_platforms], - mobile_platforms=[p.id for p in mobile_platforms]) - r = self.client.post(self.url, d, follow=True) + def post(self, expect_errors=False, data=None): + if data is None: + data = {'free': ['free-desktop']} + data.update(upload=self.upload.pk) + r = self.client.post(self.url, data, follow=True) eq_(r.status_code, 200) if not expect_errors: # Show any unexpected form errors. @@ -238,9 +229,9 @@ class BaseWebAppTest(BaseUploadTest, UploadAddon, amo.tests.TestCase): self.client.post(reverse('submit.app.terms'), {'read_dev_agreement': True}) - def post_addon(self): + def post_addon(self, data=None): eq_(Addon.objects.count(), 0) - self.post() + self.post(data=data) return Addon.objects.get() @@ -320,6 +311,20 @@ class TestCreateWebApp(BaseWebAppTest): eq_(len(files), 1) eq_(files[0].status, amo.STATUS_PENDING) + def test_set_platform(self): + app = self.post_addon({'free': ['free-tablet', 'free-desktop']}) + eq_(set(app.device_types), + set([amo.DEVICE_TABLET, amo.DEVICE_DESKTOP])) + + def test_free(self): + app = self.post_addon({'free': ['free-os']}) + eq_(app.premium_type, amo.ADDON_FREE) + + def test_premium(self): + self.create_switch('allow-b2g-paid-submission') + app = self.post_addon({'paid': ['paid-os']}) + eq_(app.premium_type, amo.ADDON_PREMIUM) + class TestCreateWebAppFromManifest(BaseWebAppTest): @@ -380,16 +385,16 @@ class BasePackagedAppTest(BaseUploadTest, UploadAddon, amo.tests.TestCase): self.package = self.packaged_app_path('mozball.zip') self.upload = self.get_upload(abspath=self.package) self.upload.update(name='mozball.zip', is_webapp=True) - self.url = reverse('submit.app.package') + self.url = reverse('submit.app.manifest') assert self.client.login(username='regular@mozilla.com', password='password') # Complete first step. self.client.post(reverse('submit.app.terms'), {'read_dev_agreement': True}) - def post_addon(self): + def post_addon(self, data=None): eq_(Addon.objects.count(), 1) - self.post() + self.post(data=data) return Addon.objects.order_by('-id')[0] def setup_files(self): @@ -413,7 +418,7 @@ class TestCreatePackagedApp(BasePackagedAppTest): reverse('submit.app.details', args=[webapp.app_slug])) def test_app_from_uploaded_package(self): - addon = self.post_addon() + addon = self.post_addon(data={'packaged': True, 'free': ['free-os']}) eq_(addon.type, amo.ADDON_WEBAPP) eq_(addon.current_version.version, '1.0') eq_(addon.is_packaged, True) @@ -429,7 +434,7 @@ class TestCreatePackagedApp(BasePackagedAppTest): @mock.patch('mkt.submit.forms.verify_app_domain') def test_packaged_app_not_unique_by_domain(self, _verify): - self.post() + self.post(data={'packaged': True, 'free': ['free-os']}) assert not _verify.called, ('`verify_app_domain` should not be called' ' for packaged apps.') diff --git a/mkt/submit/urls.py b/mkt/submit/urls.py index b876a32373..a33d1ba775 100644 --- a/mkt/submit/urls.py +++ b/mkt/submit/urls.py @@ -31,8 +31,6 @@ urlpatterns = decorate(write, patterns('', url('^app$', views.submit, name='submit.app'), url('^app/proceed$', views.proceed, name='submit.app.proceed'), url('^app/terms$', views.terms, name='submit.app.terms'), - url('^app/choose$', views.choose, name='submit.app.choose'), url('^app/manifest$', views.manifest, name='submit.app.manifest'), - url('^app/package$', views.package, name='submit.app.package'), ('^app/', include(submit_apps_patterns)), )) diff --git a/mkt/submit/views.py b/mkt/submit/views.py index 068959d93e..24635cbd24 100644 --- a/mkt/submit/views.py +++ b/mkt/submit/views.py @@ -37,8 +37,6 @@ def submit(request): # If dev has already agreed, continue to next step. user = UserProfile.objects.get(pk=request.user.id) if user.read_dev_agreement: - if waffle.switch_is_active('allow-packaged-app-uploads'): - return redirect('submit.app.choose') return redirect('submit.app.manifest') else: return redirect('submit.app.terms') @@ -66,8 +64,6 @@ def terms(request): # If dev has already agreed, continue to next step. if (getattr(request, 'amo_user', None) and request.amo_user.read_dev_agreement): - if waffle.switch_is_active('allow-packaged-app-uploads'): - return redirect('submit.app.choose') return redirect('submit.app.manifest') agreement_form = forms.DevAgreementForm( @@ -75,8 +71,6 @@ def terms(request): instance=request.amo_user) if request.POST and agreement_form.is_valid(): agreement_form.save() - if waffle.switch_is_active('allow-packaged-app-uploads'): - return redirect('submit.app.choose') return redirect('submit.app.manifest') return jingo.render(request, 'submit/terms.html', { 'step': 'terms', @@ -84,27 +78,27 @@ def terms(request): }) -@login_required -@read_dev_agreement_required -@submit_step('manifest') -def choose(request): - if not waffle.switch_is_active('allow-packaged-app-uploads'): - return redirect('submit.app.manifest') - return jingo.render(request, 'submit/choose.html', { - 'step': 'manifest', - }) - - @login_required @read_dev_agreement_required @submit_step('manifest') @transaction.commit_on_success def manifest(request): form = forms.NewWebappForm(request.POST or None) + if request.method == 'POST' and form.is_valid(): addon = Addon.from_upload( form.cleaned_data['upload'], - [Platform.objects.get(id=amo.PLATFORM_ALL.id)]) + [Platform.objects.get(id=amo.PLATFORM_ALL.id)], + is_packaged=form.is_packaged()) + + # Set the device type. + for device in form.get_devices(): + addon.addondevicetype_set.create(device_type=device.id) + + # Set the premium type, only bother if it's not free. + premium = form.get_paid() + if premium: + addon.update(premium_type=premium) if addon.has_icon_in_manifest(): # Fetch the icon, do polling. @@ -123,37 +117,7 @@ def manifest(request): return jingo.render(request, 'submit/manifest.html', { 'step': 'manifest', - 'form': form, - }) - - -@login_required -@read_dev_agreement_required -@submit_step('manifest') -def package(request): - form = forms.NewWebappForm(request.POST or None, is_packaged=True) - if request.method == 'POST' and form.is_valid(): - addon = Addon.from_upload( - form.cleaned_data['upload'], - [Platform.objects.get(id=amo.PLATFORM_ALL.id)], is_packaged=True) - - if addon.has_icon_in_manifest(): - # Fetch the icon, do polling. - addon.update(icon_type='image/png') - tasks.fetch_icon.delay(addon) - else: - # In this case there is no need to do any polling. - addon.update(icon_type='') - - AddonUser(addon=addon, user=request.amo_user).save() - AppSubmissionChecklist.objects.create(addon=addon, terms=True, - manifest=True) - - return redirect('submit.app.details', addon.app_slug) - - return jingo.render(request, 'submit/upload.html', { - 'form': form, - 'step': 'manifest', + 'form': form })