merge in packaged uploads, device and payment choices to submission (bug 778882)
This commit is contained in:
Родитель
48146b9673
Коммит
1e89fb6fd5
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 8.7 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 10 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 31 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 31 KiB |
|
@ -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');
|
||||
|
||||
$('<strong>').text(message).appendTo(upload_results);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
@ -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 = $('<hgroup></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();
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
|
@ -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.');
|
|
@ -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',
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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'))]) }}
|
||||
<header class="submit-header c">
|
||||
<h1 class="submit">{{ _('Submit an App') }}</h1>
|
||||
{{ progress(request, addon=None, step=step) }}
|
||||
</header>
|
||||
<section id="submit-choose" class="primary">
|
||||
<h2>{{ _('Choose an App Type') }}</h2>
|
||||
<section id="upload-file" class="c">
|
||||
<div class="island">
|
||||
<p>
|
||||
{% trans url=url('submit.app.manifest') %}
|
||||
If the app is <b>hosted on your own server</b>,
|
||||
then your app type is<br>
|
||||
<a href="{{ url }}" class="button">Hosted</a>
|
||||
{% endtrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans %}
|
||||
<b>What's next</b>: submit your app manifest URL
|
||||
{% endtrans %}
|
||||
</p>
|
||||
<p class="learn-mdn"><a href="{{ hosted_url }}" target="_blank">
|
||||
{% trans %}
|
||||
Learn more about <b>app manifests</b> on MDN.
|
||||
{% endtrans %}
|
||||
</a></p>
|
||||
{# TODO(bug 784448): Remove when other a.soon is gone. #}
|
||||
<span class="soon"> </a>
|
||||
</div>
|
||||
<div class="island">
|
||||
<p>
|
||||
{% trans url=url('submit.app.package') %}
|
||||
If the app is going to be <b>hosted by Mozilla</b>,
|
||||
then your app type is<br>
|
||||
<a href="{{ url }}" class="button">Packaged</a>
|
||||
{% endtrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans %}
|
||||
<b>What's next</b>: upload your packaged app
|
||||
{% endtrans %}
|
||||
</p>
|
||||
<p class="learn-mdn"><a href="{{ packaged_url }}" target="_blank">
|
||||
{% trans %}
|
||||
Learn more about <b>packaged apps</b> on MDN.
|
||||
{% endtrans %}
|
||||
</a></p>
|
||||
<a href="{{ packaged_url }}" class="soon">Coming soon</a>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="island full">
|
||||
<h2>{{ _('Frequently Asked Questions') }} <a href="{{ packaged_url }}" class="soon">Coming soon</a></h2>
|
||||
<ul>
|
||||
<li><a href="{{ packaged_url }}">What are the differences between a Hosted App and a Packaged App?</a></li>
|
||||
<li><a href="{{ packaged_url }}">Hosted App submission checklist</a></li>
|
||||
<li><a href="{{ packaged_url }}">Packaged App submission checklist</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</secion>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -6,24 +6,56 @@
|
|||
|
||||
{% block title %}{{ hub_page_title(title) }}{% endblock %}
|
||||
|
||||
{% macro button(form, item) %}
|
||||
<div class="wrapper">
|
||||
<a href="#" class="island choice{{ ' selected' if form.data[item[0]] == 'on' else '' }}"
|
||||
id="{{ item[0] }}" data-value="{{ item[0] }}">
|
||||
<h3>{{ item[1] }}</h3>
|
||||
<div>{{ form.DEVICE_LOOKUP[item[0]] }}</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% block content %}
|
||||
{{ hub_breadcrumbs(items=[(None, _('Submit App'))]) }}
|
||||
<header class="submit-header c">
|
||||
<h1>{{ _('Submit an App') }}</h1>
|
||||
{{ progress(request, addon=None, step=step) }}
|
||||
</header>
|
||||
<section id="submit-manifest" class="primary">
|
||||
<h2><a href="{{ doc_url }}">{{ _("Where's Your Manifest?") }}</a></h2>
|
||||
<p>
|
||||
{% trans %}
|
||||
Kick off things by creating your app's manifest and entering its URL
|
||||
below. <a href="{{ doc_url }}" target="_blank">Learn about manifests.</a>
|
||||
{% endtrans %}
|
||||
</p>
|
||||
<section id="upload-file" class="island">
|
||||
|
||||
|
||||
<section id="submit-payment-type" class="island tabbable">
|
||||
<div class="free tab active">
|
||||
{% if waffle.switch('allow-b2g-paid-submission') %}
|
||||
<h2><a href="#">{{ _('Free') }}</a></h2>
|
||||
{% else %}
|
||||
<h2>{{ _('Device Type') }}</h2>
|
||||
{% endif %}
|
||||
<div class="error">{{ form.errors.free }}</div>
|
||||
{% for item in form.fields['free'].choices %}
|
||||
{{ button(form, item) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if waffle.switch('allow-b2g-paid-submission') %}
|
||||
<div class="paid tab">
|
||||
<h2><a href="#">{{ _('Paid') }}</a></h2>
|
||||
<div class="error">{{ form.errors.paid }}</div>
|
||||
{% for item in form.fields['paid'].choices %}
|
||||
{{ button(form, item) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<section id="upload-file" class="island tabbable {% if waffle.switch('allow-packaged-app-uploads') %}spacer{% endif %}">
|
||||
<div class="hosted tab active">
|
||||
{% if waffle.switch('allow-packaged-app-uploads') %}
|
||||
<h2><a href="#">Hosted</a></h2>
|
||||
{% endif %}
|
||||
<h3>{{ _("Submit your app manifest URL") }}</h3>
|
||||
<div class="upload-status">
|
||||
<label>
|
||||
{{ _('Submit your app manifest URL:') }}
|
||||
<form id="validate-field">
|
||||
<div class="vf-text">
|
||||
<input type="text" id="upload-webapp-url" name="manifest" class="large"
|
||||
|
@ -34,47 +66,58 @@
|
|||
</div>
|
||||
</form>
|
||||
</label>
|
||||
<div class="upload-details">
|
||||
<div id="validate-error-protocol" class="pretty-tooltip tl">
|
||||
<span class="protocol">
|
||||
{% trans http='http://', https='https://' %}
|
||||
<strong>Don't forget a protocol!</strong>
|
||||
Try adding either <a href="#">{{ http }}</a> or
|
||||
<a href="#">{{ https }}</a>.
|
||||
{% endtrans %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="hint">
|
||||
{{ _('Manifest URLs must start with a protocol (for example, '
|
||||
'<code>http://</code> or <code>https://</code>) and '
|
||||
</div>
|
||||
<div class="upload-details">
|
||||
<div id="validate-error-protocol" class="pretty-tooltip tl">
|
||||
<span class="protocol">
|
||||
{% trans http='http://', https='https://' %}
|
||||
<strong>Don't forget a protocol!</strong>
|
||||
Try adding either <a href="#">{{ http }}</a> or
|
||||
<a href="#">{{ https }}</a>.
|
||||
{% endtrans %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="hint">
|
||||
{{ _('Manifest URLs must start with a protocol (for example, '
|
||||
'<code>http://</code> or <code>https://</code>) and '
|
||||
'typically use the <code>.webapp</code> extension.')|safe }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" id="upload-webapp">
|
||||
{{ csrf() }}
|
||||
<!--
|
||||
{{ form.non_field_errors() }}
|
||||
{{ form.upload.errors }}
|
||||
-->
|
||||
<div class="hidden">
|
||||
{{ form.upload }}
|
||||
</div>
|
||||
<footer class="listing-footer hidden">
|
||||
{% trans %}
|
||||
<b>What's next:</b> fill out the rest of your app's details
|
||||
{% endtrans %}
|
||||
<button class="upload-file-submit prominent" type="submit">
|
||||
{{ _('Continue') }}
|
||||
</button>
|
||||
</footer>
|
||||
</form>
|
||||
</section>
|
||||
{# TODO: Feel free to add more content, links to tips, sample manifests, builders. Please do. Really. #}
|
||||
<p class="learn-mdn"><a href="{{ doc_url }}" target="_blank">
|
||||
{% trans %}
|
||||
Learn more about <b>app manifests</b> on MDN.
|
||||
{% endtrans %}
|
||||
</a></p>
|
||||
</div>
|
||||
|
||||
<div class="packaged tab">
|
||||
{% if waffle.switch('allow-packaged-app-uploads') %}
|
||||
<h2><a href="#">Packaged</a></h2>
|
||||
<input type="file" id="upload-app" data-upload-url="{{ url('mkt.developers.upload') }}" />
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<form method="post" id="upload-webapp">
|
||||
{{ csrf() }}
|
||||
<!--
|
||||
{{ form.non_field_errors() }}
|
||||
{{ form.upload.errors }}
|
||||
-->
|
||||
<div class="hidden">
|
||||
{{ form.upload }}
|
||||
{{ form.free }}
|
||||
{{ form.paid }}
|
||||
{{ form.packaged }}
|
||||
</div>
|
||||
<footer class="listing-footer hidden">
|
||||
{% trans %}
|
||||
<b>What's next:</b> fill out the rest of your app's details
|
||||
{% endtrans %}
|
||||
<button class="upload-file-submit prominent" type="submit">
|
||||
{{ _('Continue') }}
|
||||
</button>
|
||||
</footer>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<p class="learn-mdn"><a href="{{ doc_url }}" target="_blank">
|
||||
{% trans %}Learn more about <b>app manifests</b> on MDN.{% endtrans %}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -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)]) }}
|
||||
<header class="submit-header c">
|
||||
<h1>{{ title }}</h1>
|
||||
{{ progress(request, addon=None, step=step) }}
|
||||
</header>
|
||||
<section id="submit-upload" class="primary">
|
||||
<h2>{{ _("Where's Your Packaged App?") }}</h2>
|
||||
<form method="post" id="create-addon" class="item">
|
||||
{{ csrf() }}
|
||||
<p>
|
||||
{% 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 %}
|
||||
</p>
|
||||
<section id="upload-file" class="island">
|
||||
<div class="hidden">
|
||||
{{ form.upload }}
|
||||
</div>
|
||||
<input type="file" id="upload-app" data-upload-url="{{ url('mkt.developers.upload') }}">
|
||||
{{ form.non_field_errors() }}
|
||||
{{ form.upload.errors }}
|
||||
<div class="submission-buttons addon-submission-field">
|
||||
<button class="prominent addon-upload-dependant" disabled id="submit-upload-file-finish" type="submit">
|
||||
{{ _('Continue') }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -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'])
|
|
@ -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.')
|
||||
|
||||
|
|
|
@ -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)),
|
||||
))
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче