New listed and unlisted addon flow (#3697)
This commit is contained in:
Родитель
2762f18b70
Коммит
df7b455edd
|
@ -18,6 +18,7 @@ from olympia import amo, paypal
|
|||
from olympia.addons.forms import AddonFormBasic
|
||||
from olympia.addons.models import (
|
||||
Addon, AddonDependency, AddonUser, Charity, Preview)
|
||||
from olympia.amo.fields import HttpHttpsOnlyURLField
|
||||
from olympia.amo.forms import AMOModelForm
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.applications.models import AppVersion
|
||||
|
@ -530,13 +531,6 @@ class NewAddonForm(AddonUploadForm):
|
|||
coerce=int,
|
||||
error_messages={'required': 'Need at least one platform.'}
|
||||
)
|
||||
is_unlisted = forms.BooleanField(
|
||||
initial=False,
|
||||
required=False,
|
||||
label=_lazy(u'Do not list my add-on on this site'),
|
||||
help_text=_lazy(
|
||||
u'Check this option if you intend to distribute your add-on on '
|
||||
u'your own and only need it to be signed by Mozilla.'))
|
||||
|
||||
def clean(self):
|
||||
if not self.errors:
|
||||
|
@ -546,6 +540,16 @@ class NewAddonForm(AddonUploadForm):
|
|||
return self.cleaned_data
|
||||
|
||||
|
||||
class StandaloneValidationForm(AddonUploadForm):
|
||||
is_unlisted = forms.BooleanField(
|
||||
initial=False,
|
||||
required=False,
|
||||
label=_lazy(u'Do not list my add-on on this site'),
|
||||
help_text=_lazy(
|
||||
u'Check this option if you intend to distribute your add-on on '
|
||||
u'your own and only need it to be signed by Mozilla.'))
|
||||
|
||||
|
||||
class NewVersionForm(NewAddonForm):
|
||||
beta = forms.BooleanField(
|
||||
required=False,
|
||||
|
@ -674,13 +678,24 @@ FileFormSet = modelformset_factory(File, formset=BaseFileFormSet,
|
|||
form=FileForm, can_delete=True, extra=0)
|
||||
|
||||
|
||||
class Step3Form(AddonFormBasic):
|
||||
description = TransField(widget=TransTextarea, required=False)
|
||||
class DescribeForm(AddonFormBasic):
|
||||
tags = None
|
||||
support_url = TransField.adapt(HttpHttpsOnlyURLField)(required=False)
|
||||
support_email = TransField.adapt(forms.EmailField)(required=False)
|
||||
|
||||
class Meta:
|
||||
model = Addon
|
||||
fields = ('name', 'slug', 'summary', 'description', 'is_experimental')
|
||||
fields = ('name', 'slug', 'summary', 'is_experimental', 'support_url',
|
||||
'support_email')
|
||||
|
||||
|
||||
class ReviewerNotesForm(happyforms.ModelForm):
|
||||
approvalnotes = forms.CharField(
|
||||
widget=TranslationTextarea(attrs={'rows': 4}), required=False)
|
||||
|
||||
class Meta:
|
||||
model = Version
|
||||
fields = ('approvalnotes',)
|
||||
|
||||
|
||||
class PreviewForm(happyforms.ModelForm):
|
||||
|
@ -801,3 +816,23 @@ def DependencyFormSet(*args, **kw):
|
|||
FormSet = modelformset_factory(AddonDependency, formset=_FormSet,
|
||||
form=_Form, extra=0, can_delete=True)
|
||||
return FormSet(*args, **kw)
|
||||
|
||||
|
||||
class DistributionChoiceForm(happyforms.Form):
|
||||
LISTED_LABEL = '%s <span class="helptext">%s</span>' % (
|
||||
_(u'On this site.'),
|
||||
_(u'Your submission will be listed on this site and the Firefox '
|
||||
u'Add-ons Manager for millions of users, after it passes code '
|
||||
u'review. Automatic updates are handled by this site. This add-on '
|
||||
u'will also be considered for Mozilla promotions and contests. '
|
||||
u'Self-distribution of the reviewed files is also possible.'))
|
||||
UNLISTED_LABEL = '%s <span class="helptext">%s</span>' % (
|
||||
_(u'On my own.'),
|
||||
_(u'This version will be immediately signed for self-distribution. '
|
||||
u'Updates are handled manually via an updateURL or external '
|
||||
u'application updates.'))
|
||||
|
||||
choices = forms.ChoiceField(
|
||||
choices=(('listed', mark_safe(LISTED_LABEL)),
|
||||
('unlisted', mark_safe(UNLISTED_LABEL)),),
|
||||
widget=forms.RadioSelect(attrs={'class': 'channel'}))
|
||||
|
|
|
@ -13,10 +13,8 @@
|
|||
{{ dev_breadcrumbs(items=[(None, page_title)]) }}
|
||||
<h2 class="is_addon">{{ title }}</h2>
|
||||
</header>
|
||||
<section class="secondary" role="complementary">
|
||||
{% include "devhub/addons/submit/sidebar.html" %}
|
||||
</section>
|
||||
<section class="primary addon-submission-process" role="main">
|
||||
|
||||
<section class="addon-submission-process" role="main">
|
||||
{% block primary %}{% endblock %}
|
||||
</section>
|
||||
{% endblock content %}
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
{% from "devhub/includes/macros.html" import some_html_tip, select_cats %}
|
||||
{% extends "devhub/addons/submit/base.html" %}
|
||||
|
||||
{% block title %}{{ dev_page_title(_('Step 3'), addon) }}{% endblock %}
|
||||
{% block title %}{{ dev_page_title(_('Describe Add-on'), addon) }}{% endblock %}
|
||||
|
||||
{% block primary %}
|
||||
<h3>{{ _('Step 3. Describe') }}</h3>
|
||||
<h3>{{ _('Describe Add-on') }}</h3>
|
||||
<form method="post" id="submit-describe" class="item{% if not addon.is_listed %} unlisted{% endif %}">
|
||||
{{ csrf() }}
|
||||
<div class="addon-submission-field">
|
||||
<label for="id_name">{{ _("Name and version:") }}</label>
|
||||
<label for="id_name">{{ _("Name:") }}</label>
|
||||
{{ form.name }}
|
||||
|
||||
{% set version = addon.current_version %}
|
||||
<input type="text" disabled id="current_version"
|
||||
value="{{ version.version }}" size="6">
|
||||
{{ form.name.errors }}
|
||||
</div>
|
||||
<div class="addon-submission-field slug-edit">
|
||||
<label>{{ _("The detail page will be:") }}</label>
|
||||
<label>{{ _("Add-on URL:") }}</label>
|
||||
<div id="slug_edit" class="edit_with_prefix edit_initially_hidden">
|
||||
<span>{{ settings.SITE_URL }}</span>{{ form.slug }}
|
||||
<div class="edit-addon-details">
|
||||
|
@ -31,54 +27,102 @@
|
|||
{{ form.slug.errors }}
|
||||
</div>
|
||||
<div class="addon-submission-field">
|
||||
<label>{{ _('Provide a brief summary:') }}</label>
|
||||
<label>{{ _('Summary:') }}</label>
|
||||
{{ form.summary }}
|
||||
{{ form.summary.errors }}
|
||||
<div class="edit-addon-details">
|
||||
{% if addon.is_listed %}
|
||||
{% trans %}
|
||||
This summary will be shown in listings and searches.
|
||||
{% endtrans %}
|
||||
{% endif %}
|
||||
{{ _('This summary will be shown in listings and searches.') }}
|
||||
<div class="char-count"
|
||||
data-for-startswith="{{ form.summary.auto_id }}_"
|
||||
data-maxlength="{{ form.summary.field.max_length }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% if addon.is_listed %}
|
||||
<div class="addon-submission-field">
|
||||
<label for="{{ form.is_experimental.auto_id }}">
|
||||
{{ form.is_experimental }}
|
||||
{{ _('This add-on is experimental') }}
|
||||
<span class="tip tooltip"
|
||||
title="{{ _('Check this option if your add-on is experimental '
|
||||
'or otherwise not ready for general use. The '
|
||||
'add-on will be listed but will have reduced '
|
||||
'visibility. You can change this setting later.')
|
||||
}}">?</span>
|
||||
<div class="addon-submission-field">
|
||||
<label for="{{ form.is_experimental.auto_id }}">
|
||||
{{ form.is_experimental }}
|
||||
{{ _('This add-on is experimental') }}
|
||||
<span class="tip tooltip"
|
||||
title="{{ _('Check this option if your add-on is experimental '
|
||||
'or otherwise not ready for general use. The '
|
||||
'add-on will be listed but will have reduced '
|
||||
'visibility. You can change this setting later.')
|
||||
}}">?</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="addon-categories-edit" class="addon-submission-field"
|
||||
data-max-categories="{{ amo.MAX_CATEGORIES }}">
|
||||
{{ cat_form.non_form_errors() }}
|
||||
{{ cat_form.management_form }}
|
||||
{% for form in cat_form.initial_forms %}
|
||||
{{ select_cats(amo.MAX_CATEGORIES, form) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="addon-categories-edit" class="addon-submission-field"
|
||||
data-max-categories="{{ amo.MAX_CATEGORIES }}">
|
||||
{{ cat_form.non_form_errors() }}
|
||||
{{ cat_form.management_form }}
|
||||
{% for form in cat_form.initial_forms %}
|
||||
{{ select_cats(amo.MAX_CATEGORIES, form) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="addon-submission-field">
|
||||
<label for="{{ form.support_email.auto_id }}">
|
||||
{{ _('Support email:') }}
|
||||
</label>
|
||||
{{ form.support_email }}
|
||||
{{ form.support_email.errors }}
|
||||
</div>
|
||||
<div class="addon-submission-field">
|
||||
<label for="{{ form.support_url.auto_id }}">
|
||||
{{ _('Support website:') }}
|
||||
</label>
|
||||
{{ form.support_url }}
|
||||
{{ form.support_url.errors }}
|
||||
</div>
|
||||
{% if license_form %}
|
||||
<b>{{ _('License:') }}</b>
|
||||
<div class="addon-submission-field">
|
||||
<label>{{ _('Provide a more detailed description:') }}</label>
|
||||
{{ form.description }}
|
||||
{{ form.description.errors }}
|
||||
<div class="edit-addon-details">
|
||||
{{ _("The description will appear on the detail page.") }}
|
||||
{{ some_html_tip() }}
|
||||
</div>
|
||||
{% trans %}
|
||||
Please choose a license appropriate for the rights you grant on your
|
||||
source code.
|
||||
{% endtrans %}
|
||||
{{ license_form.builtin.errors }}
|
||||
{{ license_form.builtin }}
|
||||
{% set show_other = (license_form.initial.builtin == license_other_val or
|
||||
(license_form.errors and not license_form.builtin.errors)) %}
|
||||
<div class="license-other {{ 'js-hidden' if not show_other }}"
|
||||
data-val="{{ license_other_val }}">
|
||||
{{ license_form.non_field_errors() }}
|
||||
{{ license_form.name.errors }}
|
||||
{{ license_form.name.label_tag() }}
|
||||
{{ license_form.name }}
|
||||
{{ license_form.text.errors }}
|
||||
{{ license_form.text.label_tag() }}
|
||||
{{ license_form.text }}
|
||||
{{ some_html_tip() }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% set values = policy_form.data if policy_form.is_bound else policy_form.initial %}
|
||||
<div class="optional-terms">
|
||||
<div class="addon-submission-field">
|
||||
{{ policy_form.has_priv }}
|
||||
{{ policy_form.has_priv.label_tag() }}
|
||||
<span class="tip tooltip"
|
||||
title="{{ _("If your add-on transmits any data from the user's computer, "
|
||||
"a privacy policy is required that explains what data is sent "
|
||||
"and how it is used.")
|
||||
}}">?</span>
|
||||
<div class="priv {{ 'hidden' if not values.has_priv }}">
|
||||
{{ policy_form.privacy_policy.errors }}
|
||||
{{ policy_form.privacy_policy.label_tag() }}
|
||||
{{ policy_form.privacy_policy }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="addon-submission-field">
|
||||
<label for="{{ reviewer_form.approvalnotes.auto_id }}">
|
||||
{{ _('Notes to Reviewer:') }}
|
||||
</label>
|
||||
<p>{{ _('Is there anything our reviewers should bear in mind when reviewing this add-on?') }}</p>
|
||||
{{ reviewer_form.approvalnotes }}
|
||||
<p>{{ _('These notes will only be visible to you and our reviewers.') }}</p>
|
||||
</div>
|
||||
<div class="submission-buttons addon-submission-field">
|
||||
<button type="submit">
|
||||
{{ _('Continue') }}
|
||||
{{ _('Submit Add-on for Review') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{% extends "devhub/addons/submit/base.html" %}
|
||||
|
||||
{% block title %}{{ dev_page_title(_('Where to Host Add-on'), addon) }}{% endblock %}
|
||||
|
||||
{% block primary %}
|
||||
|
||||
<form method="post" class="item addon-submit-distribute">
|
||||
{{ csrf() }}
|
||||
<h3>{{ _('Where to Host Add-on') }}</h3>
|
||||
<div>
|
||||
{{ distribution_form.choices }}
|
||||
</div>
|
||||
<p><a href="https://developer.mozilla.org/en-US/Add-ons/Distribution"
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
{{ _('More information on Add-on Distribution and Signing') }}</a></p>
|
||||
<div class="submission-buttons addon-submission-field">
|
||||
<button type="submit">
|
||||
{{ _('Continue') }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
{% endblock primary %}
|
|
@ -5,82 +5,46 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block primary %}
|
||||
<h3>{{ _("You're done!") }}</h3>
|
||||
{% set upload_version = addon.current_version %}
|
||||
{% if addon.is_listed %}
|
||||
<h3>{{ _("Version Submitted for Review") }}</h3>
|
||||
<p>
|
||||
{% if addon.status == amo.STATUS_NOMINATED %}
|
||||
{{ _('Your add-on has been submitted to the New Add-on queue.') }}
|
||||
{% endif %}
|
||||
{{ _("You’re done! This version has been submitted for review. You will be "
|
||||
"notified when the review has been completed, or if our reviewers have "
|
||||
"any questions about your submission.") }}
|
||||
</p>
|
||||
<p>
|
||||
{{ _("You'll receive an email once it has been reviewed by an editor and "
|
||||
"signed. Once it has been signed you will be able to install it:") }}
|
||||
{{ _("Your listing will be more successful by adding a detailed description "
|
||||
"and screenshots. Get your listing ready for publication:") }}
|
||||
</p>
|
||||
<p>
|
||||
<a id="submitted-addon-url" href="{{ addon.get_url_path() }}">
|
||||
{{ addon.get_url_path()|absolutify|display_url }}</a>
|
||||
<a class="button" href="{{ addon.get_dev_url() }}">
|
||||
{{ _("Manage Listing") }}</a>
|
||||
</p>
|
||||
<div class="done-next-steps">
|
||||
<p><strong>{{ _('Next steps:') }}</strong></p>
|
||||
<ul>
|
||||
{% if is_platform_specific %}
|
||||
{% set files_url = url('devhub.versions.edit',
|
||||
addon.slug, addon.current_version.id) %}
|
||||
<li>{{ _('<a href="{0}">Upload</a> another platform-specific file to this version.')|fe(files_url) }}</li>
|
||||
{% endif %}
|
||||
<li>{{ _('Provide more details by <a href="{0}">editing its listing</a>.')|fe(addon.get_dev_url()) }}</li>
|
||||
<li>{{ _('Tell your users why you created this in your <a href="{0}">Developer Profile</a>.')|fe(addon.get_dev_url('profile')) }}</li>
|
||||
<li>{{ _('View and subscribe to your add-on\'s <a href="{0}">activity feed</a> to stay updated on reviews, collections, and more.')|fe(url('devhub.feed', addon.slug)) }}</li>
|
||||
<li>{{ _('View approximate review queue <a href="{0}">wait times</a>.')|fe('https://blog.mozilla.org/addons/tag/add-on-reviews/') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="editor-pitch" class="action-needed">
|
||||
<h3>{{ _('Get Ahead in the Review Queue!') }}</h3>
|
||||
|
||||
<p>
|
||||
{{ _('Become an AMO Reviewer today and get your add-ons reviewed faster.') }}
|
||||
<a class="button learn-more" href="https://wiki.mozilla.org/AMO:Editors">
|
||||
{{ _('Learn More') }}</a>
|
||||
{{ _("You can also edit this version you just submitted by adding version "
|
||||
"notes, files for other platforms, or source code if your submission "
|
||||
"includes minified, obfuscated or compiled code.") }}
|
||||
</p>
|
||||
<p>
|
||||
{% set version_edit_url = url('devhub.versions.edit', addon.slug, upload_version.id) %}
|
||||
<a class="button" href="{{ version_edit_url }}">{{
|
||||
_("Edit version {0}")|fe(upload_version.version) }}</a>
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{% set signed = addon.status == amo.STATUS_PUBLIC %}
|
||||
{% if signed %}
|
||||
<p>
|
||||
{{ _('Your add-on has been signed and it\'s ready to use. You can download it here:') }}
|
||||
</p>
|
||||
<p>
|
||||
{% set version_url = url('devhub.versions.edit', addon.slug, addon.current_version.id) %}
|
||||
<a id="download-addon-url" href="{{ version_url }}">
|
||||
{{ version_url|absolutify|display_url }}</a>
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
{% if addon.status == amo.STATUS_NOMINATED %}
|
||||
{{ _('Your add-on has been submitted to the Unlisted New Add-on queue.') }}
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
{{ _('You\'ll receive an email once it has been reviewed by an editor and signed.') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
<h3>{{ _("Version Signed") }}</h3>
|
||||
<p>
|
||||
<strong>{{ _('Your add-on will not be publicly available on this website.') }}</strong>
|
||||
{{ _("You’re done! This version is signed and ready to for self-distribution."
|
||||
"You can download it by clicking the button below.") }}
|
||||
</p>
|
||||
<p>
|
||||
{% set file = upload_version.all_files[0] %}
|
||||
<a class="button" id="download-addon-url" href="{{ file.get_url_path('devhub') }}">{{
|
||||
_("Download {0}")|fe(file.pretty_filename()) }}</a>
|
||||
</p>
|
||||
<p>
|
||||
<a class="button" href="{{ url('devhub.addons') }}">
|
||||
{{ _("Return to My Submissions") }}</a>
|
||||
</p>
|
||||
<div class="done-next-steps">
|
||||
<p><strong>{{ _('Next steps:') }}</strong></p>
|
||||
<ul>
|
||||
{% if is_platform_specific %}
|
||||
{% set files_url = url('devhub.versions.edit',
|
||||
addon.slug, addon.current_version.id) %}
|
||||
<li>{{ _('<a href="{0}">Upload</a> another platform-specific file to this version.')|fe(files_url) }}</li>
|
||||
{% endif %}
|
||||
<li>{{ _('You can upload new versions of your add-on in the <a href="{0}">add-on\'s developer page</a>.')|fe(addon.get_dev_url()) }}</li>
|
||||
{% if not signed %}
|
||||
<li>{{ _('View approximate review queue <a href="{0}">wait times</a>.')|fe('https://blog.mozilla.org/addons/tag/add-on-reviews/') }}</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
{% from "devhub/includes/macros.html" import some_html_tip %}
|
||||
{% extends "devhub/addons/submit/base.html" %}
|
||||
|
||||
{% block title %}{{ dev_page_title(_('Step 5'), addon) }}{% endblock %}
|
||||
|
||||
{% block primary %}
|
||||
<h3>{{ _('Step 5. Select a License') }}</h3>
|
||||
{% if license_form %}
|
||||
<p>{% trans %}
|
||||
We require all add-ons to indicate the terms under which their source code is
|
||||
licensed. Please select a license from the list below or enter a custom license.
|
||||
{% endtrans %}</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" class="item devhub-form submit-license">
|
||||
{{ csrf() }}
|
||||
{% if license_form %}
|
||||
<b>{{ _('Select a license for your add-on:') }}</b>
|
||||
<div class="addon-submission-field">
|
||||
{{ license_form.builtin.errors }}
|
||||
{{ license_form.builtin }}
|
||||
{% set show_other = (license_form.initial.builtin == license_other_val or
|
||||
(license_form.errors and not license_form.builtin.errors)) %}
|
||||
<div class="license-other {{ 'js-hidden' if not show_other }}"
|
||||
data-val="{{ license_other_val }}">
|
||||
{{ license_form.non_field_errors() }}
|
||||
{{ license_form.name.errors }}
|
||||
{{ license_form.name.label_tag() }}
|
||||
{{ license_form.name }}
|
||||
{{ license_form.text.errors }}
|
||||
{{ license_form.text.label_tag() }}
|
||||
{{ license_form.text }}
|
||||
{{ some_html_tip() }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% set values = policy_form.data if policy_form.is_bound else policy_form.initial %}
|
||||
<div class="optional-terms">
|
||||
<div>
|
||||
{{ policy_form.has_eula }}
|
||||
{{ policy_form.has_eula.label_tag() }}
|
||||
<div class="eula {{ 'hidden' if not values.has_eula }}">
|
||||
{{ policy_form.eula.errors }}
|
||||
{{ policy_form.eula.label_tag() }}
|
||||
{{ policy_form.eula }}
|
||||
{{ some_html_tip() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="addon-submission-field">
|
||||
{{ policy_form.has_priv }}
|
||||
{{ policy_form.has_priv.label_tag() }}
|
||||
<div class="priv {{ 'hidden' if not values.has_priv }}">
|
||||
{{ policy_form.privacy_policy.errors }}
|
||||
{{ policy_form.privacy_policy.label_tag() }}
|
||||
{{ policy_form.privacy_policy }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="submission-buttons addon-submission-field">
|
||||
<button type="submit">{{ _('Continue') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,18 +0,0 @@
|
|||
{% extends "devhub/addons/submit/base.html" %}
|
||||
|
||||
{% block title %}{{ dev_page_title(_('Step 4'), addon) }}{% endblock %}
|
||||
|
||||
{% block primary %}
|
||||
<h3>{{ _('Step 4. Add Images') }}</h3>
|
||||
<form method="post" class="item" enctype="multipart/form-data" id="submit-media">
|
||||
<p>
|
||||
{% trans %}
|
||||
Custom icons and screenshots draw attention and help users
|
||||
understand what it does. We strongly recommend uploading a custom icon, as in
|
||||
some areas of the website, only the icon and name will appear.
|
||||
{% endtrans %}
|
||||
</p>
|
||||
{% set context = 'submit' %}
|
||||
{% include "devhub/addons/forms_shared/media.html" %}
|
||||
</form>
|
||||
{% endblock primary %}
|
|
@ -1,44 +0,0 @@
|
|||
{# Steps below HAS_ADDON don't have an addon associated. Once we have an
|
||||
addon we can't go back below that line. #}
|
||||
{% set HAS_ADDON = 3 %}
|
||||
{# List of steps: (text, class). A class of 'all' means the step is relevant
|
||||
to listed or unlisted addons, a class of 'listed' means it's only relevant
|
||||
to listed addons, not unlisted ones. #}
|
||||
{% set NAV = ((_('Getting Started'), 'all'),
|
||||
(_('Upload your add-on'), 'all'),
|
||||
(_('Describe your add-on'), 'all'),
|
||||
(_('Add images'), 'listed'),
|
||||
(_('Select a license'), 'listed'),
|
||||
(_("You're done!"), 'all')) %}
|
||||
|
||||
{% set MAX = 6 %}
|
||||
{% set BASE = 'devhub.submit.%s' %}
|
||||
<div class="highlight">
|
||||
<hgroup>
|
||||
<h3>{{ _('Submission Process') }}</h3>
|
||||
</hgroup>
|
||||
<ol class="submit-addon-progress{% if addon and not addon.is_listed %} unlisted{% endif %}">
|
||||
{% for text, class in NAV %}
|
||||
<li class="{{ class }}{% if step.current == loop.index %} current{% endif %}">
|
||||
{% if step.current < HAS_ADDON %}
|
||||
{# 1. Don't link to future steps.
|
||||
2. If step 1 is complete (agreement accepted), don't link to it. #}
|
||||
{% if loop.index > step.current or (loop.index == 1 and step.current > 1)%}
|
||||
{{ text }}
|
||||
{% else %}
|
||||
<a href="{{ url(BASE % loop.index) }}">{{ text }}</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{# 1. We have an addon, so don't link to non-addon steps.
|
||||
2. Don't link steps above the max step the addon has reached.
|
||||
3. If step.max == MAX the addon is done, so we only show the final page. #}
|
||||
{% if loop.index < HAS_ADDON or loop.index > step.max or step.max == MAX %}
|
||||
{{ text }}
|
||||
{% else %}
|
||||
<a href="{{ url(BASE % loop.index, addon.slug) }}">{{ text }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</div>
|
|
@ -1,10 +1,10 @@
|
|||
{% extends "devhub/addons/submit/base.html" %}
|
||||
|
||||
|
||||
{% block title %}{{ dev_page_title(_('Step 1'), addon) }}{% endblock %}
|
||||
{% block title %}{{ dev_page_title(_('Getting Started'), addon) }}{% endblock %}
|
||||
|
||||
{% block primary %}
|
||||
<h3>{{ _('Step 1. Getting Started') }}</h3>
|
||||
<h3>{{ _('Getting Started') }}</h3>
|
||||
|
||||
{% include "devhub/agreement.html" %}
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
{% extends "devhub/addons/submit/base.html" %}
|
||||
|
||||
{% block title %}{{ dev_page_title(_('Step 2'), addon) }}{% endblock %}
|
||||
{% block title %}{{ dev_page_title(_('Upload Add-on'), addon) }}{% endblock %}
|
||||
|
||||
{% block primary %}
|
||||
|
||||
<form method="post" id="create-addon" class="item new-addon-file" enctype="multipart/form-data">
|
||||
<form method="post" id="create-addon" class="item new-addon-file" enctype="multipart/form-data"
|
||||
data-addon-is-listed="{% if listed %}true{% else %}false{% endif %}">
|
||||
{{ csrf() }}
|
||||
<h3>{{ _('Step 2. Upload Your Add-on') }}</h3>
|
||||
<h3>{{ _('Upload Add-on') }}</h3>
|
||||
<p>
|
||||
{% trans %}
|
||||
Use the fields below to upload your add-on package and select any platform
|
||||
|
@ -19,21 +20,13 @@
|
|||
<div class="hidden">
|
||||
{{ new_addon_form.upload }}
|
||||
</div>
|
||||
<div class="list-addon">
|
||||
<label>{{ _('Do you want your add-on to be distributed on this site?') }}</label>
|
||||
<div>
|
||||
<label for="{{ new_addon_form.is_unlisted.auto_id }}">
|
||||
{{ new_addon_form.is_unlisted }}
|
||||
{{ new_addon_form.is_unlisted.label }}
|
||||
<span class="tip tooltip"
|
||||
title="{{ new_addon_form.is_unlisted.help_text }}">?</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" id="upload-addon"
|
||||
{% if listed %}
|
||||
data-upload-url="{{ url('devhub.upload') }}"
|
||||
data-upload-url-listed="{{ url('devhub.upload') }}"
|
||||
data-upload-url-unlisted="{{ url('devhub.upload_unlisted') }}">
|
||||
{% else %}
|
||||
data-upload-url="{{ url('devhub.upload_unlisted') }}"
|
||||
{% endif %}
|
||||
>
|
||||
|
||||
{{ new_addon_form.non_field_errors() }}
|
||||
|
||||
|
|
|
@ -17,14 +17,13 @@ import mock
|
|||
import pytest
|
||||
import waffle
|
||||
from jingo.helpers import datetime as datetime_filter
|
||||
from PIL import Image
|
||||
from pyquery import PyQuery as pq
|
||||
|
||||
from olympia import amo, paypal, files
|
||||
from olympia.amo.tests import TestCase, version_factory
|
||||
from olympia.addons.models import (
|
||||
Addon, AddonCategory, AddonFeatureCompatibility, Category, Charity)
|
||||
from olympia.amo.helpers import absolutify, user_media_path, url as url_reverse
|
||||
from olympia.amo.helpers import url as url_reverse
|
||||
from olympia.amo.tests import addon_factory, formset, initial
|
||||
from olympia.amo.tests.test_helpers import get_image_path
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
|
@ -1268,7 +1267,7 @@ class TestAPIKeyPage(TestCase):
|
|||
assert 'revoked' in mail.outbox[0].body
|
||||
|
||||
|
||||
class TestSubmitStep1(TestSubmitBase):
|
||||
class TestSubmitStepAgreement(TestSubmitBase):
|
||||
def test_step1_submit(self):
|
||||
self.user.update(read_dev_agreement=None)
|
||||
response = self.client.get(reverse('devhub.submit.1'))
|
||||
|
@ -1298,43 +1297,48 @@ class TestSubmitStep1(TestSubmitBase):
|
|||
self.assert3xx(response, reverse('devhub.submit.2'))
|
||||
|
||||
|
||||
class TestSubmitStep2(TestCase):
|
||||
# More tests in TestCreateAddon.
|
||||
class TestSubmitStepDistribute(TestCase):
|
||||
fixtures = ['base/users']
|
||||
|
||||
def setUp(self):
|
||||
super(TestSubmitStep2, self).setUp()
|
||||
super(TestSubmitStepDistribute, self).setUp()
|
||||
self.client.login(email='regular@mozilla.com')
|
||||
self.user = UserProfile.objects.get(email='regular@mozilla.com')
|
||||
|
||||
def test_step_2_seen(self):
|
||||
def test_check_agreement_okay(self):
|
||||
r = self.client.post(reverse('devhub.submit.1'))
|
||||
self.assert3xx(r, reverse('devhub.submit.2'))
|
||||
r = self.client.get(reverse('devhub.submit.2'))
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_step_2_not_seen(self):
|
||||
def test_redirect_back_to_agreement(self):
|
||||
# We require a cookie that gets set in step 1.
|
||||
self.user.update(read_dev_agreement=None)
|
||||
|
||||
r = self.client.get(reverse('devhub.submit.2'), follow=True)
|
||||
self.assert3xx(r, reverse('devhub.submit.1'))
|
||||
|
||||
def test_step_2_listed_checkbox(self):
|
||||
# There is a checkbox for the "is_listed" addon field.
|
||||
self.client.post(reverse('devhub.submit.1'))
|
||||
response = self.client.get(reverse('devhub.submit.2'))
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content)
|
||||
assert doc('.list-addon input#id_is_unlisted[type=checkbox]')
|
||||
def test_listed_redirects_to_next_step(self):
|
||||
response = self.client.post(reverse('devhub.submit.2'),
|
||||
{'choices': 'listed'})
|
||||
self.assert3xx(response, reverse('devhub.submit.3', args=['listed']))
|
||||
|
||||
def test_unlisted_redirects_to_next_step(self):
|
||||
response = self.client.post(reverse('devhub.submit.2'),
|
||||
{'choices': 'unlisted'})
|
||||
self.assert3xx(response, reverse('devhub.submit.3',
|
||||
args=['unlisted']))
|
||||
|
||||
|
||||
class TestSubmitStep3(TestSubmitBase):
|
||||
# Tests for Upload step in TestCreateAddon
|
||||
|
||||
|
||||
class TestSubmitStepDescribe(TestSubmitBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSubmitStep3, self).setUp()
|
||||
self.url = reverse('devhub.submit.3', args=['a3615'])
|
||||
SubmitStep.objects.create(addon_id=3615, step=3)
|
||||
super(TestSubmitStepDescribe, self).setUp()
|
||||
self.url = reverse('devhub.submit.4', args=['a3615'])
|
||||
SubmitStep.objects.create(addon_id=3615, step=4)
|
||||
|
||||
AddonCategory.objects.filter(
|
||||
addon=self.get_addon(),
|
||||
|
@ -1345,77 +1349,79 @@ class TestSubmitStep3(TestSubmitBase):
|
|||
|
||||
ctx = self.client.get(self.url).context['cat_form']
|
||||
self.cat_initial = initial(ctx.initial_forms[0])
|
||||
self.next_step = reverse('devhub.submit.5', args=['a3615'])
|
||||
License.objects.create(builtin=3, on_form=True)
|
||||
self.get_addon().update(status=amo.STATUS_NULL)
|
||||
|
||||
def get_dict(self, **kw):
|
||||
def get_dict(self, minimal=True, **kw):
|
||||
result = {}
|
||||
describe_form = {'name': 'Test name', 'slug': 'testname',
|
||||
'summary': 'Hello!', 'is_experimental': True}
|
||||
if not minimal:
|
||||
describe_form.update({'support_url': 'http://stackoverflow.com',
|
||||
'support_email': 'black@hole.org'})
|
||||
cat_initial = kw.pop('cat_initial', self.cat_initial)
|
||||
fs = formset(cat_initial, initial_count=1)
|
||||
result = {'name': 'Test name', 'slug': 'testname',
|
||||
'description': 'desc', 'summary': 'Hello!',
|
||||
'is_experimental': True}
|
||||
cat_form = formset(cat_initial, initial_count=1)
|
||||
license_form = {'builtin': 3}
|
||||
policy_form = {} if minimal else {
|
||||
'has_priv': True, 'privacy_policy': 'Ur data belongs to us now.'}
|
||||
reviewer_form = {} if minimal else {'approvalnotes': 'approove plz'}
|
||||
result.update(describe_form)
|
||||
result.update(cat_form)
|
||||
result.update(license_form)
|
||||
result.update(policy_form)
|
||||
result.update(reviewer_form)
|
||||
result.update(**kw)
|
||||
result.update(fs)
|
||||
return result
|
||||
|
||||
def test_submit_success(self):
|
||||
def is_success(self, data):
|
||||
assert self.get_addon().status == amo.STATUS_NULL
|
||||
r = self.client.post(self.url, data)
|
||||
assert r.status_code == 302
|
||||
assert self.get_addon().status == amo.STATUS_NOMINATED
|
||||
pytest.raises(SubmitStep.DoesNotExist, self.get_step)
|
||||
return r
|
||||
|
||||
def test_submit_success_minimal(self):
|
||||
# Set/change the required fields only
|
||||
r = self.client.get(self.url)
|
||||
assert r.status_code == 200
|
||||
|
||||
# Post and be redirected - trying to sneak
|
||||
# in fields that shouldn't be modified via this form.
|
||||
d = self.get_dict(homepage='foo.com',
|
||||
support_email='foo@mozilla.com',
|
||||
support_url='baz.com',
|
||||
tags='whatevs, whatever')
|
||||
r = self.client.post(self.url, d)
|
||||
assert r.status_code == 302
|
||||
assert self.get_step().step == 4
|
||||
self.is_success(d)
|
||||
|
||||
addon = self.get_addon()
|
||||
|
||||
# This fields should not have been modified.
|
||||
assert addon.homepage != 'foo.com'
|
||||
assert addon.support_email != 'foo@mozilla.com'
|
||||
assert addon.support_url != 'baz.com'
|
||||
assert len(addon.tags.values_list()) == 0
|
||||
|
||||
# These are the field that are expected to be
|
||||
# edited here.
|
||||
# These are the fields that are expected to be edited here.
|
||||
assert addon.name == 'Test name'
|
||||
assert addon.slug == 'testname'
|
||||
assert addon.description == 'desc'
|
||||
assert addon.summary == 'Hello!'
|
||||
assert addon.is_experimental
|
||||
|
||||
# Test add-on log activity.
|
||||
log_items = ActivityLog.objects.for_addons(addon)
|
||||
assert not log_items.filter(action=amo.LOG.EDIT_DESCRIPTIONS.id), (
|
||||
"Creating a description needn't be logged.")
|
||||
assert not log_items.filter(action=amo.LOG.EDIT_PROPERTIES.id), (
|
||||
"Setting properties on submit needn't be logged.")
|
||||
|
||||
def test_submit_unlisted_addon(self):
|
||||
self.addon.update(is_listed=False)
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Post and be redirected.
|
||||
response = self.client.post(self.url, {'name': 'unlisted addon',
|
||||
'slug': 'unlisted-addon',
|
||||
'summary': 'summary'})
|
||||
assert response.status_code == 302
|
||||
assert response.url.endswith(reverse('devhub.submit.6',
|
||||
args=['unlisted-addon']))
|
||||
# Unlisted addons don't need much info, and their queue is chosen
|
||||
# automatically on step 2, so we skip steps 4, 5 and 6. We thus have no
|
||||
# more steps at that point.
|
||||
assert not SubmitStep.objects.filter(addon=self.addon).exists()
|
||||
def test_submit_success_optional_fields(self):
|
||||
# Set/change the optional fields too
|
||||
# Post and be redirected
|
||||
d = self.get_dict(minimal=False)
|
||||
self.is_success(d)
|
||||
|
||||
addon = self.get_addon()
|
||||
assert addon.name == 'unlisted addon'
|
||||
assert addon.slug == 'unlisted-addon'
|
||||
assert addon.summary == 'summary'
|
||||
# Test add-on log activity.
|
||||
log_items = ActivityLog.objects.for_addons(addon)
|
||||
assert not log_items.filter(action=amo.LOG.EDIT_DESCRIPTIONS.id), (
|
||||
"Creating a description needn't be logged.")
|
||||
|
||||
# These are the fields that are expected to be edited here.
|
||||
assert addon.support_url == 'http://stackoverflow.com'
|
||||
assert addon.support_email == 'black@hole.org'
|
||||
assert addon.privacy_policy == 'Ur data belongs to us now.'
|
||||
|
||||
def test_submit_name_unique(self):
|
||||
# Make sure name is unique.
|
||||
|
@ -1431,22 +1437,7 @@ class TestSubmitStep3(TestSubmitBase):
|
|||
assert get_addon_count('Cooliris') == 1
|
||||
# It's allowed for the '3615' listed add-on to reuse the same name as
|
||||
# the other 'Cooliris' unlisted add-on.
|
||||
response = self.client.post(self.url, self.get_dict(name='Cooliris'))
|
||||
assert response.status_code == 302
|
||||
assert get_addon_count('Cooliris') == 2
|
||||
|
||||
def test_submit_unlisted_name_not_unique(self):
|
||||
"""Unlisted add-ons names aren't unique."""
|
||||
# Change the existing add-on with the 'Cooliris' name to be unlisted.
|
||||
Addon.objects.get(name__localized_string='Cooliris').update(
|
||||
is_listed=False)
|
||||
# Change the '3615' add-on to be unlisted.
|
||||
Addon.objects.get(pk=3615).update(is_listed=False)
|
||||
assert get_addon_count('Cooliris') == 1
|
||||
# It's allowed for the '3615' unlisted add-on to reuse the same name as
|
||||
# the other 'Cooliris' unlisted add-on.
|
||||
response = self.client.post(self.url, self.get_dict(name='Cooliris'))
|
||||
assert response.status_code == 302
|
||||
self.is_success(self.get_dict(name='Cooliris'))
|
||||
assert get_addon_count('Cooliris') == 2
|
||||
|
||||
def test_submit_name_unique_strip(self):
|
||||
|
@ -1516,7 +1507,7 @@ class TestSubmitStep3(TestSubmitBase):
|
|||
assert [c.id for c in self.get_addon().all_categories] == [22]
|
||||
self.cat_initial['categories'] = [22, 1]
|
||||
|
||||
self.client.post(self.url, self.get_dict())
|
||||
self.is_success(self.get_dict())
|
||||
|
||||
addon_cats = self.get_addon().categories.values_list('id', flat=True)
|
||||
assert sorted(addon_cats) == [1, 22]
|
||||
|
@ -1542,232 +1533,33 @@ class TestSubmitStep3(TestSubmitBase):
|
|||
category_ids_new = [cat.id for cat in self.get_addon().all_categories]
|
||||
assert category_ids_new == [22]
|
||||
|
||||
def test_check_version(self):
|
||||
r = self.client.get(self.url)
|
||||
doc = pq(r.content)
|
||||
version = doc("#current_version").val()
|
||||
|
||||
assert version == self.addon.current_version.version
|
||||
|
||||
|
||||
class TestSubmitStep4(TestSubmitBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSubmitStep4, self).setUp()
|
||||
SubmitStep.objects.create(addon_id=3615, step=4)
|
||||
self.url = reverse('devhub.submit.4', args=['a3615'])
|
||||
self.next_step = reverse('devhub.submit.5', args=['a3615'])
|
||||
self.icon_upload = reverse('devhub.addons.upload_icon',
|
||||
args=['a3615'])
|
||||
self.preview_upload = reverse('devhub.addons.upload_preview',
|
||||
args=['a3615'])
|
||||
|
||||
def test_get(self):
|
||||
assert self.client.get(self.url).status_code == 200
|
||||
|
||||
def test_post(self):
|
||||
data = dict(icon_type='')
|
||||
data_formset = self.formset_media(**data)
|
||||
r = self.client.post(self.url, data_formset)
|
||||
assert r.status_code == 302
|
||||
assert 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_icon_upload_attributes(self):
|
||||
doc = pq(self.client.get(self.url).content)
|
||||
field = doc('input[name=icon_upload]')
|
||||
assert field.length == 1
|
||||
assert sorted(field.attr('data-allowed-types').split('|')) == (
|
||||
['image/jpeg', 'image/png'])
|
||||
assert field.attr('data-upload-url') == self.icon_upload
|
||||
|
||||
def test_edit_media_defaulticon(self):
|
||||
data = dict(icon_type='')
|
||||
data_formset = self.formset_media(**data)
|
||||
|
||||
self.client.post(self.url, data_formset)
|
||||
|
||||
addon = self.get_addon()
|
||||
|
||||
assert addon.get_icon_url(64).endswith('icons/default-64.png')
|
||||
|
||||
for k in data:
|
||||
assert unicode(getattr(addon, k)) == data[k]
|
||||
|
||||
def test_edit_media_preuploadedicon(self):
|
||||
data = dict(icon_type='icon/appearance')
|
||||
data_formset = self.formset_media(**data)
|
||||
self.client.post(self.url, data_formset)
|
||||
|
||||
addon = self.get_addon()
|
||||
|
||||
assert '/'.join(addon.get_icon_url(64).split('/')[-2:]) == (
|
||||
'addon-icons/appearance-64.png')
|
||||
|
||||
for k in data:
|
||||
assert unicode(getattr(addon, k)) == data[k]
|
||||
|
||||
def test_edit_media_uploadedicon(self):
|
||||
with open(get_image_path('mozilla.png'), 'rb') as filehandle:
|
||||
data = {'upload_image': filehandle}
|
||||
response = self.client.post(self.icon_upload, data)
|
||||
response_json = json.loads(response.content)
|
||||
addon = self.get_addon()
|
||||
|
||||
# Now, save the form so it gets moved properly.
|
||||
data = dict(icon_type='image/png',
|
||||
icon_upload_hash=response_json['upload_hash'])
|
||||
data_formset = self.formset_media(**data)
|
||||
|
||||
self.client.post(self.url, data_formset)
|
||||
|
||||
addon = self.get_addon()
|
||||
|
||||
# Sad we're hardcoding /3/ here, but that's how the URLs work
|
||||
_url = addon.get_icon_url(64).split('?')[0]
|
||||
assert _url.endswith('addon_icons/3/%s-64.png' % addon.id)
|
||||
|
||||
assert data['icon_type'] == 'image/png'
|
||||
|
||||
# Check that it was actually uploaded
|
||||
dirname = os.path.join(user_media_path('addon_icons'),
|
||||
'%s' % (addon.id / 1000))
|
||||
dest = os.path.join(dirname, '%s-32.png' % addon.id)
|
||||
|
||||
assert storage.exists(dest)
|
||||
|
||||
assert Image.open(storage.open(dest)).size == (32, 12)
|
||||
|
||||
def test_edit_media_uploadedicon_noresize(self):
|
||||
with open('static/img/notifications/error.png', 'rb') as filehandle:
|
||||
data = {'upload_image': filehandle}
|
||||
response = self.client.post(self.icon_upload, data)
|
||||
response_json = json.loads(response.content)
|
||||
addon = self.get_addon()
|
||||
|
||||
# Now, save the form so it gets moved properly.
|
||||
data = dict(icon_type='image/png',
|
||||
icon_upload_hash=response_json['upload_hash'])
|
||||
data_formset = self.formset_media(**data)
|
||||
|
||||
self.client.post(self.url, data_formset)
|
||||
addon = self.get_addon()
|
||||
|
||||
# Sad we're hardcoding /3/ here, but that's how the URLs work
|
||||
_url = addon.get_icon_url(64).split('?')[0]
|
||||
assert _url.endswith('addon_icons/3/%s-64.png' % addon.id)
|
||||
|
||||
assert data['icon_type'] == 'image/png'
|
||||
|
||||
# Check that it was actually uploaded
|
||||
dirname = os.path.join(user_media_path('addon_icons'),
|
||||
'%s' % (addon.id / 1000))
|
||||
dest = os.path.join(dirname, '%s-64.png' % addon.id)
|
||||
|
||||
assert storage.exists(dest)
|
||||
|
||||
assert Image.open(storage.open(dest)).size == (48, 48)
|
||||
|
||||
def test_client_lied(self):
|
||||
with open(get_image_path('non-animated.gif'), 'rb') as filehandle:
|
||||
data = {'upload_image': filehandle}
|
||||
res = self.client.post(self.preview_upload, data)
|
||||
response_json = json.loads(res.content)
|
||||
assert response_json['errors'][0] == (
|
||||
u'Images must be either PNG or JPG.')
|
||||
|
||||
def test_client_error_triggers_tmp_image_cleanup(self):
|
||||
with open(get_image_path('non-animated.gif'), 'rb') as filehandle:
|
||||
data = {'upload_image': filehandle, 'upload_type': 'preview'}
|
||||
self.client.post(self.preview_upload, data)
|
||||
assert not os.listdir(os.path.join(settings.TMP_PATH, 'preview'))
|
||||
|
||||
def test_image_animated(self):
|
||||
with open(get_image_path('animated.png'), 'rb') as filehandle:
|
||||
data = {'upload_image': filehandle}
|
||||
res = self.client.post(self.preview_upload, data)
|
||||
response_json = json.loads(res.content)
|
||||
assert response_json['errors'][0] == u'Images cannot be animated.'
|
||||
|
||||
def test_icon_non_animated(self):
|
||||
with open(get_image_path('non-animated.png'), 'rb') as filehandle:
|
||||
data = {'icon_type': 'image/png', 'icon_upload': filehandle}
|
||||
data_formset = self.formset_media(**data)
|
||||
res = self.client.post(self.url, data_formset)
|
||||
assert res.status_code == 302
|
||||
assert self.get_step().step == 5
|
||||
|
||||
|
||||
class TestSubmitStep5(TestSubmitBase):
|
||||
"""License submission."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestSubmitStep5, self).setUp()
|
||||
SubmitStep.objects.create(addon_id=self.addon.id, step=5)
|
||||
self.url = reverse('devhub.submit.5', args=['a3615'])
|
||||
self.next_step = reverse('devhub.submit.6', args=['a3615'])
|
||||
License.objects.create(builtin=3, on_form=True)
|
||||
|
||||
def test_get(self):
|
||||
assert self.client.get(self.url).status_code == 200
|
||||
|
||||
def test_set_license(self):
|
||||
r = self.client.post(self.url, {'builtin': 3})
|
||||
self.assert3xx(r, self.next_step)
|
||||
def test_set_license_no_log(self):
|
||||
self.is_success(self.get_dict(builtin=3))
|
||||
assert self.get_addon().current_version.license.builtin == 3
|
||||
pytest.raises(SubmitStep.DoesNotExist, self.get_step)
|
||||
log_items = ActivityLog.objects.for_addons(self.get_addon())
|
||||
assert not log_items.filter(action=amo.LOG.CHANGE_LICENSE.id), (
|
||||
"Initial license choice:6 needn't be logged.")
|
||||
assert not log_items.filter(action=amo.LOG.CHANGE_LICENSE.id)
|
||||
|
||||
def test_license_error(self):
|
||||
r = self.client.post(self.url, {'builtin': 4})
|
||||
r = self.client.post(self.url, self.get_dict(builtin=4))
|
||||
assert r.status_code == 200
|
||||
self.assertFormError(r, 'license_form', 'builtin',
|
||||
'Select a valid choice. 4 is not one of '
|
||||
'the available choices.')
|
||||
assert self.get_step().step == 5
|
||||
assert self.get_step().step == 4
|
||||
|
||||
def test_set_eula(self):
|
||||
self.get_addon().update(eula=None, privacy_policy=None)
|
||||
r = self.client.post(self.url, dict(builtin=3, has_eula=True,
|
||||
eula='xxx'))
|
||||
self.assert3xx(r, self.next_step)
|
||||
assert unicode(self.get_addon().eula) == 'xxx'
|
||||
pytest.raises(SubmitStep.DoesNotExist, self.get_step)
|
||||
|
||||
def test_set_eula_nomsg(self):
|
||||
def test_set_privacy_nomsg(self):
|
||||
"""
|
||||
You should not get punished with a 500 for not writing your EULA...
|
||||
You should not get punished with a 500 for not writing your policy...
|
||||
but perhaps you should feel shame for lying to us. This test does not
|
||||
test for shame.
|
||||
"""
|
||||
self.get_addon().update(eula=None, privacy_policy=None)
|
||||
r = self.client.post(self.url, dict(builtin=3, has_eula=True))
|
||||
self.assert3xx(r, self.next_step)
|
||||
pytest.raises(SubmitStep.DoesNotExist, self.get_step)
|
||||
self.is_success(self.get_dict(has_priv=True))
|
||||
|
||||
def test_status_is_nominated(self):
|
||||
def test_nomination_date_set_only_once(self):
|
||||
self.get_version().update(nomination=None)
|
||||
r = self.client.post(self.url, {'builtin': 3})
|
||||
assert r.status_code == 302
|
||||
addon = self.get_addon()
|
||||
assert addon.status == amo.STATUS_NOMINATED
|
||||
self.is_success(self.get_dict())
|
||||
self.assertCloseToNow(self.get_version().nomination)
|
||||
pytest.raises(SubmitStep.DoesNotExist, self.get_step)
|
||||
|
||||
# Check nomination date is only set once, see bug 632191.
|
||||
nomdate = datetime.now() - timedelta(days=5)
|
||||
|
@ -1778,11 +1570,11 @@ class TestSubmitStep5(TestSubmitBase):
|
|||
nomdate.timetuple()[0:5])
|
||||
|
||||
|
||||
class TestSubmitStep6(TestSubmitBase):
|
||||
class TestSubmitStepDone(TestSubmitBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSubmitStep6, self).setUp()
|
||||
self.url = reverse('devhub.submit.6', args=[self.addon.slug])
|
||||
super(TestSubmitStepDone, self).setUp()
|
||||
self.url = reverse('devhub.submit.5', args=[self.addon.slug])
|
||||
|
||||
@mock.patch.object(settings, 'SITE_URL', 'http://b.ro')
|
||||
@mock.patch('olympia.devhub.tasks.send_welcome_email.delay')
|
||||
|
@ -1808,7 +1600,7 @@ class TestSubmitStep6(TestSubmitBase):
|
|||
assert not send_welcome_email_mock.called
|
||||
|
||||
@mock.patch('olympia.devhub.tasks.send_welcome_email.delay', new=mock.Mock)
|
||||
def test_finish_submitting_addon(self):
|
||||
def test_finish_submitting_listed_addon(self):
|
||||
assert self.addon.current_version.supported_platforms == (
|
||||
[amo.PLATFORM_ALL])
|
||||
|
||||
|
@ -1816,79 +1608,36 @@ class TestSubmitStep6(TestSubmitBase):
|
|||
assert r.status_code == 200
|
||||
doc = pq(r.content)
|
||||
|
||||
a = doc('a#submitted-addon-url')
|
||||
url = self.addon.get_url_path()
|
||||
assert a.attr('href') == url
|
||||
assert a.text() == absolutify(url)
|
||||
|
||||
next_steps = doc('.done-next-steps li a')
|
||||
|
||||
# edit listing of freshly submitted add-on...
|
||||
assert next_steps.eq(0).attr('href') == self.addon.get_dev_url()
|
||||
|
||||
# edit your developer profile...
|
||||
assert next_steps.eq(1).attr('href') == (
|
||||
self.addon.get_dev_url('profile'))
|
||||
content = doc('.addon-submission-process')
|
||||
links = content('a')
|
||||
assert len(links) == 2
|
||||
# First link is to edit listing
|
||||
assert links[0].attrib['href'] == self.addon.get_dev_url()
|
||||
# Second link is to edit the version
|
||||
assert links[1].attrib['href'] == reverse(
|
||||
'devhub.versions.edit',
|
||||
args=[self.addon.slug, self.addon.current_version.id])
|
||||
assert links[1].text == (
|
||||
'Edit version %s' % self.addon.current_version.version)
|
||||
|
||||
@mock.patch('olympia.devhub.tasks.send_welcome_email.delay', new=mock.Mock)
|
||||
def test_finish_submitting_unlisted_addon(self):
|
||||
self.addon.update(is_listed=False, status=amo.STATUS_NOMINATED)
|
||||
|
||||
r = self.client.get(self.url)
|
||||
assert r.status_code == 200
|
||||
doc = pq(r.content)
|
||||
|
||||
# For unlisted add-ons, there's only the devhub page link displayed and
|
||||
# a link to the forum page on the wait times.
|
||||
content = doc('.done-next-steps')
|
||||
assert len(content('a')) == 2
|
||||
assert content('a').eq(0).attr('href') == self.addon.get_dev_url()
|
||||
|
||||
@mock.patch('olympia.devhub.tasks.send_welcome_email.delay', new=mock.Mock)
|
||||
def test_finish_submitting_unlisted_addon_signed(self):
|
||||
self.addon.update(is_listed=False, status=amo.STATUS_PUBLIC)
|
||||
|
||||
r = self.client.get(self.url)
|
||||
assert r.status_code == 200
|
||||
doc = pq(r.content)
|
||||
|
||||
# For unlisted addon that are already signed, show a url to the devhub
|
||||
# versions page and to the addon listing.
|
||||
content = doc('.addon-submission-process')
|
||||
links = content('a')
|
||||
assert len(links) == 2
|
||||
assert links[0].attrib['href'] == reverse(
|
||||
'devhub.versions.edit',
|
||||
args=[self.addon.slug, self.addon.current_version.id])
|
||||
assert links[1].attrib['href'] == self.addon.get_dev_url()
|
||||
|
||||
@mock.patch('olympia.devhub.tasks.send_welcome_email.delay', new=mock.Mock)
|
||||
def test_finish_submitting_platform_specific_addon(self):
|
||||
# mac-only Add-on:
|
||||
addon = Addon.objects.get(name__localized_string='Cooliris')
|
||||
addon.addonuser_set.create(user_id=55021)
|
||||
r = self.client.get(reverse('devhub.submit.6', args=[addon.slug]))
|
||||
assert r.status_code == 200
|
||||
next_steps = pq(r.content)('.done-next-steps li a')
|
||||
|
||||
# upload more platform specific files...
|
||||
assert next_steps.eq(0).attr('href') == (
|
||||
reverse('devhub.versions.edit',
|
||||
kwargs=dict(addon_id=addon.slug,
|
||||
version_id=addon.current_version.id)))
|
||||
|
||||
# edit listing of freshly submitted add-on...
|
||||
assert next_steps.eq(1).attr('href') == addon.get_dev_url()
|
||||
|
||||
@mock.patch('olympia.devhub.tasks.send_welcome_email.delay', new=mock.Mock)
|
||||
def test_finish_addon_for_full_review(self):
|
||||
self.addon.update(status=amo.STATUS_NOMINATED)
|
||||
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content)
|
||||
intro = doc('.addon-submission-process p').text().strip()
|
||||
assert 'New Add-on' in intro
|
||||
# First link is to the file download.
|
||||
file_ = self.addon.latest_version.all_files[0]
|
||||
assert links[0].attrib['href'] == file_.get_url_path('devhub')
|
||||
assert links[0].text == (
|
||||
'Download %s' % file_.filename)
|
||||
# Second back to my submissions.
|
||||
assert links[1].attrib['href'] == reverse('devhub.addons')
|
||||
|
||||
@mock.patch('olympia.devhub.tasks.send_welcome_email.delay', new=mock.Mock)
|
||||
def test_incomplete_addon_no_versions(self):
|
||||
|
@ -1897,25 +1646,6 @@ class TestSubmitStep6(TestSubmitBase):
|
|||
r = self.client.get(self.url, follow=True)
|
||||
self.assert3xx(r, self.addon.get_dev_url('versions'), 302)
|
||||
|
||||
@mock.patch('olympia.devhub.tasks.send_welcome_email.delay', new=mock.Mock)
|
||||
def test_link_to_activityfeed(self):
|
||||
r = self.client.get(self.url, follow=True)
|
||||
doc = pq(r.content)
|
||||
assert doc('.done-next-steps a').eq(2).attr('href') == (
|
||||
reverse('devhub.feed', args=[self.addon.slug]))
|
||||
|
||||
@mock.patch('olympia.devhub.tasks.send_welcome_email.delay', new=mock.Mock)
|
||||
def test_display_non_ascii_url(self):
|
||||
u = 'フォクすけといっしょ'
|
||||
self.addon.update(slug=u)
|
||||
r = self.client.get(reverse('devhub.submit.6', args=[u]))
|
||||
assert r.status_code == 200
|
||||
# The meta charset will always be utf-8.
|
||||
doc = pq(r.content.decode('utf-8'))
|
||||
assert doc('#submitted-addon-url').text() == (
|
||||
u'%s/en-US/firefox/addon/%s/' % (
|
||||
settings.SITE_URL, u.decode('utf8')))
|
||||
|
||||
|
||||
class TestResumeStep(TestSubmitBase):
|
||||
|
||||
|
@ -1929,7 +1659,7 @@ class TestResumeStep(TestSubmitBase):
|
|||
|
||||
def test_step_redirects(self):
|
||||
SubmitStep.objects.create(addon_id=3615, step=1)
|
||||
for i in xrange(3, 6):
|
||||
for i in xrange(4, 5):
|
||||
SubmitStep.objects.filter(addon=self.get_addon()).update(step=i)
|
||||
r = self.client.get(self.url, follow=True)
|
||||
self.assert3xx(r, reverse('devhub.submit.%s' % i,
|
||||
|
@ -1967,93 +1697,34 @@ class TestSubmitSteps(TestCase):
|
|||
assert self.client.login(email='del@icio.us')
|
||||
self.user = UserProfile.objects.get(email='del@icio.us')
|
||||
|
||||
def assert_linked(self, doc, numbers):
|
||||
"""Check that the nth <li> in the steps list is a link."""
|
||||
lis = doc('.submit-addon-progress li')
|
||||
assert len(lis) == 6
|
||||
for idx, li in enumerate(lis):
|
||||
links = pq(li)('a')
|
||||
if (idx + 1) in numbers:
|
||||
assert len(links) == 1
|
||||
else:
|
||||
assert len(links) == 0
|
||||
|
||||
def assert_highlight(self, doc, num):
|
||||
"""Check that the nth <li> is marked as .current."""
|
||||
lis = doc('.submit-addon-progress li')
|
||||
assert pq(lis[num - 1]).hasClass('current')
|
||||
assert len(pq('.current', lis)) == 1
|
||||
|
||||
def test_step_1(self):
|
||||
self.user.update(read_dev_agreement=None)
|
||||
r = self.client.get(reverse('devhub.submit.1'))
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_on_step_5(self):
|
||||
def test_on_step_4(self):
|
||||
# Hitting the step we're supposed to be on is a 200.
|
||||
SubmitStep.objects.create(addon_id=3615, step=5)
|
||||
r = self.client.get(reverse('devhub.submit.5',
|
||||
SubmitStep.objects.create(addon_id=3615, step=4)
|
||||
r = self.client.get(reverse('devhub.submit.4',
|
||||
args=['a3615']))
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_skip_step_5(self):
|
||||
# We get bounced back to step 3.
|
||||
SubmitStep.objects.create(addon_id=3615, step=3)
|
||||
def test_skip_step_4(self):
|
||||
# We get bounced back to step 4.
|
||||
SubmitStep.objects.create(addon_id=3615, step=4)
|
||||
r = self.client.get(reverse('devhub.submit.5',
|
||||
args=['a3615']), follow=True)
|
||||
self.assert3xx(r, reverse('devhub.submit.3', args=['a3615']))
|
||||
self.assert3xx(r, reverse('devhub.submit.4', args=['a3615']))
|
||||
|
||||
def test_all_done(self):
|
||||
# There's no SubmitStep, so we must be done.
|
||||
r = self.client.get(reverse('devhub.submit.5',
|
||||
r = self.client.get(reverse('devhub.submit.4',
|
||||
args=['a3615']), follow=True)
|
||||
self.assert3xx(r, reverse('devhub.submit.6', args=['a3615']))
|
||||
self.assert3xx(r, reverse('devhub.submit.5', args=['a3615']))
|
||||
|
||||
def test_menu_step_1(self):
|
||||
self.user.update(read_dev_agreement=None)
|
||||
doc = pq(self.client.get(reverse('devhub.submit.1')).content)
|
||||
self.assert_linked(doc, [1])
|
||||
self.assert_highlight(doc, 1)
|
||||
|
||||
def test_menu_step_2(self):
|
||||
self.client.post(reverse('devhub.submit.1'))
|
||||
doc = pq(self.client.get(reverse('devhub.submit.2')).content)
|
||||
self.assert_linked(doc, [2])
|
||||
self.assert_highlight(doc, 2)
|
||||
|
||||
def test_menu_step_3(self):
|
||||
SubmitStep.objects.create(addon_id=3615, step=3)
|
||||
url = reverse('devhub.submit.3', args=['a3615'])
|
||||
doc = pq(self.client.get(url).content)
|
||||
self.assert_linked(doc, [3])
|
||||
self.assert_highlight(doc, 3)
|
||||
|
||||
def test_menu_step_3_from_5(self):
|
||||
SubmitStep.objects.create(addon_id=3615, step=5)
|
||||
url = reverse('devhub.submit.3', args=['a3615'])
|
||||
doc = pq(self.client.get(url).content)
|
||||
self.assert_linked(doc, [3, 4, 5])
|
||||
self.assert_highlight(doc, 3)
|
||||
|
||||
def test_menu_step_6(self):
|
||||
url = reverse('devhub.submit.6', args=['a3615'])
|
||||
doc = pq(self.client.get(url).content)
|
||||
self.assert_linked(doc, [])
|
||||
self.assert_highlight(doc, 6)
|
||||
|
||||
def test_menu_step_6_unlisted(self):
|
||||
SubmitStep.objects.create(addon_id=3615, step=6)
|
||||
Addon.objects.get(pk=3615).update(is_listed=False)
|
||||
url = reverse('devhub.submit.6', args=['a3615'])
|
||||
doc = pq(self.client.get(url).content)
|
||||
self.assert_linked(doc, []) # Last step: no previous step linked.
|
||||
# Skipped from step 3 to 6, as unlisted add-ons don't need listing
|
||||
# information. Thus none of the steps from 4 to 5 should be there.
|
||||
# For reference, the steps that are with the "listed" class (instead of
|
||||
# "all") aren't displayed.
|
||||
assert len(doc('.submit-addon-progress li.all')) == 4
|
||||
# The step 6 is thus the 4th visible in the list.
|
||||
self.assert_highlight(doc, 6) # Current step is still the 6th.
|
||||
def test_submit_no_step_redirects_to_done(self):
|
||||
r = self.client.get('developers/addon/a3615/submit/', follow=True)
|
||||
self.assert3xx(r, reverse('devhub.submit.5', args=['a3615']))
|
||||
|
||||
|
||||
class TestUpload(BaseUploadTest):
|
||||
|
@ -2754,10 +2425,11 @@ class TestUploadErrors(UploadTest):
|
|||
@mock.patch.object(waffle, 'flag_is_active', return_value=True)
|
||||
@mock.patch('olympia.devhub.tasks.validate')
|
||||
@mock.patch('olympia.devhub.tasks.run_validator')
|
||||
def test_dupe_xpi(self, run_validator, validate_, flag_is_active):
|
||||
def test_dupe_xpi(self, run_validator, validate_, flag_is_active, **kw):
|
||||
# Submit a new addon:
|
||||
self.client.post(reverse('devhub.submit.1')) # set cookie
|
||||
res = self.client.get(reverse('devhub.submit.2'))
|
||||
channel = kw.get('channel', 'listed')
|
||||
res = self.client.get(reverse('devhub.submit.3', args=[channel]))
|
||||
assert res.status_code == 200
|
||||
doc = pq(res.content)
|
||||
|
||||
|
@ -2771,7 +2443,7 @@ class TestUploadErrors(UploadTest):
|
|||
upload = FileUpload.objects.get(uuid=data['upload'])
|
||||
|
||||
# Check that `tasks.validate` has been called with the expected upload.
|
||||
validate_.assert_called_with(upload, listed=True)
|
||||
validate_.assert_called_with(upload, listed=(channel == 'listed'))
|
||||
|
||||
# Poll and check that we are still pending validation.
|
||||
data = json.loads(self.client.get(poll_url).content)
|
||||
|
@ -2791,7 +2463,7 @@ class TestUploadErrors(UploadTest):
|
|||
def test_dupe_xpi_unlisted_addon(self):
|
||||
"""Submitting an xpi with the same UUID as an unlisted addon."""
|
||||
self.addon.update(is_listed=False)
|
||||
self.test_dupe_xpi()
|
||||
self.test_dupe_xpi(channel='unlisted')
|
||||
|
||||
|
||||
class AddVersionTest(UploadTest):
|
||||
|
@ -3141,16 +2813,24 @@ class TestVersionXSS(UploadTest):
|
|||
assert '&lt;script&gt;alert' in r.content
|
||||
|
||||
|
||||
class UploadAddon(object):
|
||||
class TestCreateAddon(BaseUploadTest, TestCase):
|
||||
fixtures = ['base/users']
|
||||
|
||||
def setUp(self):
|
||||
super(TestCreateAddon, self).setUp()
|
||||
self.upload = self.get_upload('extension.xpi')
|
||||
assert self.client.login(email='regular@mozilla.com')
|
||||
self.client.post(reverse('devhub.submit.1'))
|
||||
|
||||
def post(self, supported_platforms=None, expect_errors=False,
|
||||
source=None, is_listed=True, status_code=200):
|
||||
if supported_platforms is None:
|
||||
supported_platforms = [amo.PLATFORM_ALL]
|
||||
d = dict(upload=self.upload.uuid.hex, source=source,
|
||||
supported_platforms=[p.id for p in supported_platforms],
|
||||
is_unlisted=not is_listed)
|
||||
r = self.client.post(self.url, d, follow=True)
|
||||
supported_platforms=[p.id for p in supported_platforms])
|
||||
url = reverse('devhub.submit.3',
|
||||
args=['listed' if is_listed else 'unlisted'])
|
||||
r = self.client.post(url, d, follow=True)
|
||||
assert r.status_code == status_code
|
||||
if not expect_errors:
|
||||
# Show any unexpected form errors.
|
||||
|
@ -3158,17 +2838,6 @@ class UploadAddon(object):
|
|||
assert r.context['new_addon_form'].errors.as_text() == ''
|
||||
return r
|
||||
|
||||
|
||||
class TestCreateAddon(BaseUploadTest, UploadAddon, TestCase):
|
||||
fixtures = ['base/users']
|
||||
|
||||
def setUp(self):
|
||||
super(TestCreateAddon, self).setUp()
|
||||
self.upload = self.get_upload('extension.xpi')
|
||||
self.url = reverse('devhub.submit.2')
|
||||
assert self.client.login(email='regular@mozilla.com')
|
||||
self.client.post(reverse('devhub.submit.1'))
|
||||
|
||||
def assert_json_error(self, *args):
|
||||
UploadTest().assert_json_error(self, *args)
|
||||
|
||||
|
@ -3210,7 +2879,7 @@ class TestCreateAddon(BaseUploadTest, UploadAddon, TestCase):
|
|||
addon = Addon.objects.get()
|
||||
assert addon.is_listed
|
||||
assert addon.latest_version.channel == amo.RELEASE_CHANNEL_LISTED
|
||||
self.assert3xx(r, reverse('devhub.submit.3', args=[addon.slug]))
|
||||
self.assert3xx(r, reverse('devhub.submit.4', args=[addon.slug]))
|
||||
log_items = ActivityLog.objects.for_addons(addon)
|
||||
assert log_items.filter(action=amo.LOG.CREATE_ADDON.id), (
|
||||
'New add-on creation never logged.')
|
||||
|
@ -3256,7 +2925,8 @@ class TestCreateAddon(BaseUploadTest, UploadAddon, TestCase):
|
|||
assert mock_sign_file.called
|
||||
|
||||
def test_missing_platforms(self):
|
||||
r = self.client.post(self.url, dict(upload=self.upload.uuid.hex))
|
||||
url = reverse('devhub.submit.3', args=['listed'])
|
||||
r = self.client.post(url, dict(upload=self.upload.uuid.hex))
|
||||
assert r.status_code == 200
|
||||
assert r.context['new_addon_form'].errors.as_text() == (
|
||||
'* supported_platforms\n * Need at least one platform.')
|
||||
|
@ -3269,7 +2939,7 @@ class TestCreateAddon(BaseUploadTest, UploadAddon, TestCase):
|
|||
r = self.post(supported_platforms=[amo.PLATFORM_MAC,
|
||||
amo.PLATFORM_LINUX])
|
||||
addon = Addon.objects.get()
|
||||
self.assert3xx(r, reverse('devhub.submit.3', args=[addon.slug]))
|
||||
self.assert3xx(r, reverse('devhub.submit.4', args=[addon.slug]))
|
||||
all_ = sorted([f.filename for f in addon.current_version.all_files])
|
||||
assert all_ == [u'xpi_name-0.1-linux.xpi', u'xpi_name-0.1-mac.xpi']
|
||||
|
||||
|
@ -3281,7 +2951,7 @@ class TestCreateAddon(BaseUploadTest, UploadAddon, TestCase):
|
|||
amo.PLATFORM_LINUX],
|
||||
is_listed=False)
|
||||
addon = Addon.unfiltered.get()
|
||||
self.assert3xx(r, reverse('devhub.submit.3', args=[addon.slug]))
|
||||
self.assert3xx(r, reverse('devhub.submit.5', args=[addon.slug]))
|
||||
all_ = sorted([f.filename for f in addon.current_version.all_files])
|
||||
assert all_ == [u'xpi_name-0.1-linux.xpi', u'xpi_name-0.1-mac.xpi']
|
||||
mock_auto_sign_file.assert_has_calls(
|
||||
|
@ -3295,7 +2965,7 @@ class TestCreateAddon(BaseUploadTest, UploadAddon, TestCase):
|
|||
assert Addon.objects.count() == 0
|
||||
r = self.post(source=source)
|
||||
addon = Addon.objects.get()
|
||||
self.assert3xx(r, reverse('devhub.submit.3', args=[addon.slug]))
|
||||
self.assert3xx(r, reverse('devhub.submit.4', args=[addon.slug]))
|
||||
assert addon.current_version.source
|
||||
assert Addon.objects.get(pk=addon.pk).admin_review
|
||||
|
||||
|
|
|
@ -14,11 +14,9 @@ PACKAGE_NAME = '(?P<package_name>[_\w]+)'
|
|||
# These will all start with /addon/<addon_id>/submit/
|
||||
submit_patterns = patterns(
|
||||
'',
|
||||
url('^$', lambda r, addon_id: redirect('devhub.submit.6', addon_id)),
|
||||
url('^3$', views.submit_describe, name='devhub.submit.3'),
|
||||
url('^4$', views.submit_media, name='devhub.submit.4'),
|
||||
url('^5$', views.submit_license, name='devhub.submit.5'),
|
||||
url('^6$', views.submit_done, name='devhub.submit.6'),
|
||||
url('^$', lambda r, addon_id: redirect('devhub.submit.5', addon_id)),
|
||||
url('^details$', views.submit_describe, name='devhub.submit.4'),
|
||||
url('^finish$', views.submit_done, name='devhub.submit.5'),
|
||||
url('^bump$', views.submit_bump, name='devhub.submit.bump'),
|
||||
)
|
||||
|
||||
|
@ -145,11 +143,14 @@ urlpatterns = decorate(write, patterns(
|
|||
# Add-on submission
|
||||
url('^addon/submit/$',
|
||||
lambda r: redirect('devhub.submit.1', permanent=True)),
|
||||
url('^addon/submit/1$', views.submit, name='devhub.submit.1'),
|
||||
url('^addon/submit/2$', views.submit_addon, name='devhub.submit.2'),
|
||||
url('^addon/submit/agreement$', views.submit, name='devhub.submit.1'),
|
||||
url('^addon/submit/distribution$', views.submit_addon_distribute,
|
||||
name='devhub.submit.2'),
|
||||
url('^addon/submit/upload-(?P<channel>listed|unlisted)$',
|
||||
views.submit_addon_upload, name='devhub.submit.3'),
|
||||
|
||||
# Submission API
|
||||
url('^addon/submit/agreement/$', views.api_key_agreement,
|
||||
url('^addon/agreement/$', views.api_key_agreement,
|
||||
name='devhub.api_key_agreement'),
|
||||
|
||||
url('^addon/api/key/$', views.api_key, name='devhub.api_key'),
|
||||
|
|
|
@ -591,8 +591,7 @@ def compat_application_versions(request):
|
|||
def validate_addon(request):
|
||||
return render(request, 'devhub/validate_addon.html',
|
||||
{'title': _('Validate Add-on'),
|
||||
# Hack: we just need the "is_unlisted" field from this form.
|
||||
'new_addon_form': forms.NewAddonForm(
|
||||
'new_addon_form': forms.StandaloneValidationForm(
|
||||
None, None, request=request)})
|
||||
|
||||
|
||||
|
@ -602,8 +601,7 @@ def check_addon_compatibility(request):
|
|||
return render(request, 'devhub/validate_addon.html',
|
||||
{'appversion_form': form,
|
||||
'title': _('Check Add-on Compatibility'),
|
||||
# Hack: we just need the "is_unlisted" field from this form.
|
||||
'new_addon_form': forms.NewAddonForm(
|
||||
'new_addon_form': forms.StandaloneValidationForm(
|
||||
None, None, request=request)})
|
||||
|
||||
|
||||
|
@ -1395,7 +1393,7 @@ def submit_step(outer_step):
|
|||
@functools.wraps(f)
|
||||
def wrapper(request, *args, **kw):
|
||||
step = outer_step
|
||||
max_step = 6
|
||||
max_step = 5
|
||||
# We only bounce on pages with an addon id.
|
||||
if 'addon' in kw:
|
||||
addon = kw['addon']
|
||||
|
@ -1407,7 +1405,7 @@ def submit_step(outer_step):
|
|||
return redirect(_step_url(max_step), addon.slug)
|
||||
elif step != max_step:
|
||||
# We couldn't find a step, so we must be done.
|
||||
return redirect(_step_url(6), addon.slug)
|
||||
return redirect(_step_url(5), addon.slug)
|
||||
kw['step'] = Step(step, max_step)
|
||||
return f(request, *args, **kw)
|
||||
# Tell @dev_required that this is a function in the submit flow so it
|
||||
|
@ -1432,22 +1430,34 @@ def submit(request, step):
|
|||
@login_required
|
||||
@submit_step(2)
|
||||
@transaction.atomic
|
||||
def submit_addon(request, step):
|
||||
def submit_addon_distribute(request, step):
|
||||
if request.user.read_dev_agreement is None:
|
||||
return redirect(_step_url(1))
|
||||
form = forms.DistributionChoiceForm(request.POST)
|
||||
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
data = form.cleaned_data
|
||||
return redirect(_step_url(3), data['choices'])
|
||||
return render(request, 'devhub/addons/submit/distribute.html',
|
||||
{'step': step, 'distribution_form': form})
|
||||
|
||||
|
||||
@login_required
|
||||
@submit_step(3)
|
||||
@transaction.atomic
|
||||
def submit_addon_upload(request, step, channel):
|
||||
form = forms.NewAddonForm(
|
||||
request.POST or None,
|
||||
request.FILES or None,
|
||||
request=request
|
||||
)
|
||||
is_listed = channel == 'listed'
|
||||
if request.method == 'POST':
|
||||
if form.is_valid():
|
||||
data = form.cleaned_data
|
||||
|
||||
p = data.get('supported_platforms', [])
|
||||
|
||||
is_listed = not data['is_unlisted']
|
||||
|
||||
addon = Addon.from_upload(data['upload'], p, source=data['source'],
|
||||
is_listed=is_listed)
|
||||
AddonUser(addon=addon, user=request.user).save()
|
||||
|
@ -1457,92 +1467,53 @@ def submit_addon(request, step):
|
|||
addon.update(status=amo.STATUS_NOMINATED)
|
||||
# Sign all the files submitted, one for each platform.
|
||||
auto_sign_version(addon.versions.get())
|
||||
SubmitStep.objects.create(addon=addon, step=3)
|
||||
return redirect(_step_url(3), addon.slug)
|
||||
return redirect('devhub.submit.5', addon.slug)
|
||||
SubmitStep.objects.create(addon=addon, step=4)
|
||||
return redirect(_step_url(4), addon.slug)
|
||||
is_admin = acl.action_allowed(request, 'ReviewerAdminTools', 'View')
|
||||
|
||||
return render(request, 'devhub/addons/submit/upload.html',
|
||||
{'step': step, 'new_addon_form': form, 'is_admin': is_admin})
|
||||
|
||||
|
||||
@dev_required
|
||||
@submit_step(3)
|
||||
def submit_describe(request, addon_id, addon, step):
|
||||
form_cls = forms.Step3Form
|
||||
form = form_cls(request.POST or None, instance=addon, request=request)
|
||||
cat_form = addon_forms.CategoryFormSet(request.POST or None, addon=addon,
|
||||
request=request)
|
||||
|
||||
if request.method == 'POST' and form.is_valid() and (
|
||||
not addon.is_listed or cat_form.is_valid()):
|
||||
addon = form.save(addon)
|
||||
submit_step = SubmitStep.objects.filter(addon=addon)
|
||||
if addon.is_listed:
|
||||
cat_form.save()
|
||||
submit_step.update(step=4)
|
||||
return redirect(_step_url(4), addon.slug)
|
||||
else: # Finished for unlisted addons.
|
||||
submit_step.delete()
|
||||
signals.submission_done.send(sender=addon)
|
||||
return redirect('devhub.submit.6', addon.slug)
|
||||
return render(request, 'devhub/addons/submit/describe.html',
|
||||
{'form': form, 'cat_form': cat_form, 'addon': addon,
|
||||
'step': step})
|
||||
{'step': step, 'new_addon_form': form, 'is_admin': is_admin,
|
||||
'listed': is_listed})
|
||||
|
||||
|
||||
@dev_required
|
||||
@submit_step(4)
|
||||
def submit_media(request, addon_id, addon, step):
|
||||
form_icon = addon_forms.AddonFormMedia(
|
||||
request.POST or None,
|
||||
request.FILES or None, instance=addon, request=request)
|
||||
form_previews = forms.PreviewFormSet(
|
||||
request.POST or None,
|
||||
prefix='files', queryset=addon.previews.all())
|
||||
def submit_describe(request, addon_id, addon, step):
|
||||
forms_, context = [], {}
|
||||
describe_form = forms.DescribeForm(request.POST or None, instance=addon,
|
||||
request=request)
|
||||
cat_form = addon_forms.CategoryFormSet(
|
||||
request.POST or None, addon=addon, request=request)
|
||||
license_form = forms.LicenseForm(request.POST or None, addon=addon)
|
||||
policy_form = forms.PolicyForm(request.POST or None, addon=addon)
|
||||
reviewer_form = forms.ReviewerNotesForm(
|
||||
request.POST or None, instance=addon.latest_version)
|
||||
|
||||
if (request.method == 'POST' and
|
||||
form_icon.is_valid() and form_previews.is_valid()):
|
||||
addon = form_icon.save(addon)
|
||||
context.update(license_form.get_context())
|
||||
context.update(form=describe_form, cat_form=cat_form,
|
||||
policy_form=policy_form, reviewer_form=reviewer_form)
|
||||
forms_.extend([describe_form, cat_form, context['license_form'],
|
||||
policy_form, reviewer_form])
|
||||
|
||||
for preview in form_previews.forms:
|
||||
preview.save(addon)
|
||||
if request.method == 'POST' and all([form.is_valid() for form in forms_]):
|
||||
addon = describe_form.save(addon)
|
||||
cat_form.save()
|
||||
license_form.save(log=False)
|
||||
policy_form.save()
|
||||
reviewer_form.save()
|
||||
addon.update(status=amo.STATUS_NOMINATED)
|
||||
|
||||
SubmitStep.objects.filter(addon=addon).update(step=5)
|
||||
SubmitStep.objects.filter(addon=addon).delete()
|
||||
signals.submission_done.send(sender=addon)
|
||||
|
||||
return redirect(_step_url(5), addon.slug)
|
||||
|
||||
return render(request, 'devhub/addons/submit/media.html',
|
||||
{'form': form_icon, 'addon': addon, 'step': step,
|
||||
'preview_form': form_previews})
|
||||
return redirect('devhub.submit.5', addon.slug)
|
||||
context.update(addon=addon, step=step)
|
||||
return render(request, 'devhub/addons/submit/describe.html', context)
|
||||
|
||||
|
||||
@dev_required
|
||||
@submit_step(5)
|
||||
def submit_license(request, addon_id, addon, step):
|
||||
fs, ctx = [], {}
|
||||
# Versions.
|
||||
license_form = forms.LicenseForm(request.POST or None, addon=addon)
|
||||
ctx.update(license_form.get_context())
|
||||
fs.append(ctx['license_form'])
|
||||
# Policy.
|
||||
policy_form = forms.PolicyForm(request.POST or None, addon=addon)
|
||||
fs.append(policy_form)
|
||||
if request.method == 'POST' and all([form.is_valid() for form in fs]):
|
||||
if license_form in fs:
|
||||
license_form.save(log=False)
|
||||
policy_form.save()
|
||||
|
||||
addon.update(status=amo.STATUS_NOMINATED)
|
||||
SubmitStep.objects.filter(addon=addon).delete()
|
||||
signals.submission_done.send(sender=addon)
|
||||
return redirect('devhub.submit.6', addon.slug)
|
||||
ctx.update(addon=addon, policy_form=policy_form, step=step)
|
||||
|
||||
return render(request, 'devhub/addons/submit/license.html', ctx)
|
||||
|
||||
|
||||
@dev_required
|
||||
@submit_step(6)
|
||||
def submit_done(request, addon_id, addon, step):
|
||||
# Bounce to the versions page if they don't have any versions.
|
||||
if not addon.versions.exists():
|
||||
|
|
|
@ -129,6 +129,10 @@ span.remove {
|
|||
cursor: help;
|
||||
}
|
||||
|
||||
.addon-submit-distribute * label span.helptext {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
span.tip:hover,
|
||||
a.remove:hover {
|
||||
background-color: #2A4364 !important;
|
||||
|
|
|
@ -70,13 +70,17 @@ ul.refinements:last-child {
|
|||
width: 80%;
|
||||
}
|
||||
|
||||
#submit-describe #trans-name {
|
||||
#submit-describe #trans-name,
|
||||
#submit-describe #trans-support_email,
|
||||
#submit-describe #trans-support_url {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
#submit-describe #trans-name input {
|
||||
#submit-describe #trans-name input,
|
||||
#submit-describe #trans-support_email input,
|
||||
#submit-describe #trans-support_url input {
|
||||
width: 370px;
|
||||
}
|
||||
|
||||
|
|
|
@ -339,7 +339,7 @@
|
|||
$('.upload-status').remove();
|
||||
}
|
||||
$isUnlistedCheckbox.bind('change', updateListedStatus);
|
||||
updateListedStatus();
|
||||
if ($isUnlistedCheckbox.length) updateListedStatus();
|
||||
|
||||
$('#id_is_manual_review').bind('change', function() {
|
||||
$('.addon-upload-dependant').prop('disabled', !($(this).is(':checked')));
|
||||
|
|
Загрузка…
Ссылка в новой задаче