add in choice of when to go public (bug 740967)
This commit is contained in:
Родитель
0ac647a972
Коммит
213eace875
|
@ -283,6 +283,7 @@ class Addon(amo.models.OnChangeMixin, amo.models.ModelBase):
|
|||
|
||||
objects = AddonManager()
|
||||
with_deleted = AddonManager(include_deleted=True)
|
||||
make_public = models.DateTimeField(null=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'addons'
|
||||
|
|
|
@ -379,6 +379,16 @@ class MANIFEST_UPDATED(_LOG):
|
|||
format = _(u'{addon} manifest updated.')
|
||||
|
||||
|
||||
class APPROVE_VERSION_WAITING(_LOG):
|
||||
id = 53
|
||||
action_class = 'approve'
|
||||
format = _(u'{addon} {version} approved but waiting to be made public.')
|
||||
short = _(u'Approved but waiting')
|
||||
keep = True
|
||||
review_email_user = True
|
||||
review_queue = True
|
||||
|
||||
|
||||
class CUSTOM_TEXT(_LOG):
|
||||
id = 98
|
||||
format = '{0}'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from datetime import datetime
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -19,6 +20,7 @@ STATUS_LITE_AND_NOMINATED = 9
|
|||
STATUS_PURGATORY = 10 # A temporary home; bug 614686
|
||||
STATUS_DELETED = 11
|
||||
STATUS_REJECTED = 12 # This applies only to apps (for now)
|
||||
STATUS_PUBLIC_WAITING = 13 # bug 740967
|
||||
|
||||
STATUS_CHOICES = {
|
||||
STATUS_NULL: _(u'Incomplete'),
|
||||
|
@ -34,18 +36,26 @@ STATUS_CHOICES = {
|
|||
_(u'Preliminarily Reviewed and Awaiting Full Review'),
|
||||
STATUS_PURGATORY: _(u'Pending a review choice'),
|
||||
STATUS_DELETED: _(u'Deleted'),
|
||||
STATUS_REJECTED: _(u'Rejected')
|
||||
STATUS_REJECTED: _(u'Rejected'),
|
||||
# Approved, but the developer would like to put it public when they want.
|
||||
# The need to go to the marketplace and actualy make it public.
|
||||
STATUS_PUBLIC_WAITING: _('Approved but waiting'),
|
||||
}
|
||||
|
||||
PUBLIC_IMMEDIATELY = None
|
||||
# Our MySQL does not store microseconds.
|
||||
PUBLIC_WAIT = datetime.max.replace(microsecond=0)
|
||||
|
||||
REVIEWED_STATUSES = (STATUS_LITE, STATUS_LITE_AND_NOMINATED, STATUS_PUBLIC)
|
||||
UNREVIEWED_STATUSES = (STATUS_UNREVIEWED, STATUS_PENDING, STATUS_NOMINATED,
|
||||
STATUS_PURGATORY)
|
||||
VALID_STATUSES = (STATUS_UNREVIEWED, STATUS_PENDING, STATUS_NOMINATED,
|
||||
STATUS_PUBLIC, STATUS_LISTED, STATUS_BETA, STATUS_LITE,
|
||||
STATUS_LITE_AND_NOMINATED, STATUS_PURGATORY)
|
||||
STATUS_LITE_AND_NOMINATED, STATUS_PURGATORY,
|
||||
STATUS_PUBLIC_WAITING)
|
||||
# We don't show addons/versions with UNREVIEWED_STATUS in public.
|
||||
LISTED_STATUSES = tuple(st for st in VALID_STATUSES
|
||||
if st not in (STATUS_PENDING,))
|
||||
if st not in (STATUS_PENDING, STATUS_PUBLIC_WAITING))
|
||||
|
||||
# An add-on in one of these statuses is awaiting a review.
|
||||
STATUS_UNDER_REVIEW = (STATUS_UNREVIEWED, STATUS_NOMINATED,
|
||||
|
|
|
@ -55,7 +55,8 @@
|
|||
.status-incomplete b,
|
||||
.status-disabled b,
|
||||
.status-admin-disabled b,
|
||||
.status-purgatory b {
|
||||
.status-purgatory b,
|
||||
.status-waiting b {
|
||||
color: #851006;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE addons ADD COLUMN `make_public` datetime;
|
|
@ -183,6 +183,7 @@ def status_class(addon):
|
|||
amo.STATUS_LITE: 'lite',
|
||||
amo.STATUS_LITE_AND_NOMINATED: 'lite-nom',
|
||||
amo.STATUS_PURGATORY: 'purgatory',
|
||||
amo.STATUS_PUBLIC_WAITING: 'waiting',
|
||||
}
|
||||
if addon.disabled_by_user and addon.status != amo.STATUS_DISABLED:
|
||||
cls = 'disabled'
|
||||
|
|
|
@ -36,6 +36,9 @@
|
|||
questions, please email app-reviews@mozilla.org.") }}
|
||||
{% elif addon.status == amo.STATUS_REJECTED %}
|
||||
{{ status(_('This app has been <b>rejected</b> by a Mozilla Marketplace reviewer.')|safe) }}
|
||||
{% elif addon.status == amo.STATUS_PUBLIC_WAITING %}
|
||||
{{ status(_('Your app has been <b>approved but is not public</b>.')|safe) }}
|
||||
{{ _('It is waiting your approval to make public.') }}
|
||||
{% endif %}
|
||||
{% if not (addon.is_disabled or addon.is_incomplete()) %}
|
||||
<a href="https://developer.mozilla.org/en/Apps/Marketplace_Review"
|
||||
|
@ -68,6 +71,11 @@
|
|||
{{ form_field(form.release_notes, opt=True) }}
|
||||
<p><button type="submit">{{ _('Resubmit App') }}</button></p>
|
||||
</form>
|
||||
{% elif addon.status == amo.STATUS_PUBLIC_WAITING %}
|
||||
<form method="post" action="{{ addon.get_dev_url('publicise') }}">
|
||||
{{ csrf() }}
|
||||
<p><button type="submit">{{ _('Make App public') }}</button></p>
|
||||
</form>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p class="version-status-actions listing-footer">
|
||||
|
|
|
@ -6,7 +6,6 @@ from decimal import Decimal
|
|||
|
||||
from django.conf import settings
|
||||
from django.core import mail
|
||||
from django.utils.http import urlencode
|
||||
|
||||
import mock
|
||||
from nose.plugins.attrib import attr
|
||||
|
@ -589,7 +588,7 @@ class TestIssueRefund(amo.tests.TestCase):
|
|||
|
||||
@mock.patch('stats.models.Contribution.enqueue_refund')
|
||||
@mock.patch('paypal.refund')
|
||||
def test_apps_issue(self, refund, enqueue_refund):
|
||||
def test_apps_issue_error(self, refund, enqueue_refund):
|
||||
refund.side_effect = PaypalError
|
||||
c = self.make_purchase()
|
||||
r = self.client.post(self.url, {'transaction_id': c.transaction_id,
|
||||
|
@ -829,6 +828,39 @@ class TestRefunds(amo.tests.TestCase):
|
|||
babel_datetime(refund.requested).strip())
|
||||
|
||||
|
||||
class TestPublicise(amo.tests.TestCase):
|
||||
fixtures = ['webapps/337141-steamcube']
|
||||
|
||||
def setUp(self):
|
||||
self.webapp = self.get_webapp()
|
||||
self.webapp.update(status=amo.STATUS_PUBLIC_WAITING)
|
||||
self.publicise_url = self.webapp.get_dev_url('publicise')
|
||||
self.status_url = self.webapp.get_dev_url('versions')
|
||||
assert self.client.login(username='steamcube@mozilla.com',
|
||||
password='password')
|
||||
|
||||
def get_webapp(self):
|
||||
return Addon.objects.no_cache().get(id=337141)
|
||||
|
||||
def test_logout(self):
|
||||
self.client.logout()
|
||||
res = self.client.post(self.publicise_url)
|
||||
eq_(res.status_code, 302)
|
||||
eq_(self.get_webapp().status, amo.STATUS_PUBLIC_WAITING)
|
||||
|
||||
def test_publicise(self):
|
||||
res = self.client.post(self.publicise_url)
|
||||
eq_(res.status_code, 302)
|
||||
eq_(self.get_webapp().status, amo.STATUS_PUBLIC)
|
||||
|
||||
def test_status(self):
|
||||
res = self.client.get(self.status_url)
|
||||
eq_(res.status_code, 200)
|
||||
doc = pq(res.content)
|
||||
eq_(doc('#version-status form').attr('action'), self.publicise_url)
|
||||
eq_(len(doc('strong.status-waiting')), 1)
|
||||
|
||||
|
||||
class TestDelete(amo.tests.TestCase):
|
||||
fixtures = ['webapps/337141-steamcube']
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ app_detail_patterns = patterns('',
|
|||
url('^enable$', views.enable, name='mkt.developers.apps.enable'),
|
||||
url('^delete$', views.delete, name='mkt.developers.apps.delete'),
|
||||
url('^disable$', views.disable, name='mkt.developers.apps.disable'),
|
||||
url('^publicise$', views.publicise, name='mkt.developers.apps.publicise'),
|
||||
url('^status$', views.status, name='mkt.developers.apps.versions'),
|
||||
|
||||
url('^payments$', views.payments, name='mkt.developers.apps.payments'),
|
||||
|
|
|
@ -185,6 +185,15 @@ def disable(request, addon_id, addon):
|
|||
return redirect(addon.get_dev_url('versions'))
|
||||
|
||||
|
||||
@dev_required
|
||||
@post_required
|
||||
def publicise(request, addon_id, addon):
|
||||
if addon.status == amo.STATUS_PUBLIC_WAITING:
|
||||
addon.update(status=amo.STATUS_PUBLIC)
|
||||
amo.log(amo.LOG.CHANGE_STATUS, addon.get_status_display(), addon)
|
||||
return redirect(addon.get_dev_url('versions'))
|
||||
|
||||
|
||||
@dev_required(webapp=True)
|
||||
def status(request, addon_id, addon, webapp=False):
|
||||
form = forms.AppAppealForm(request.POST, product=addon)
|
||||
|
|
|
@ -123,7 +123,7 @@ class TestReviewApp(AppReviewerTest, EditorTest):
|
|||
|
||||
def _check_log(self, action):
|
||||
assert AppLog.objects.filter(addon=self.app,
|
||||
activity_log__action=action.id).exists(), (
|
||||
activity_log__action=action.id).exists(), (
|
||||
"Didn't find `%s` action in logs." % action.short)
|
||||
|
||||
def test_push_public(self):
|
||||
|
@ -143,6 +143,24 @@ class TestReviewApp(AppReviewerTest, EditorTest):
|
|||
self._check_email(msg, 'App Approved')
|
||||
self._check_email_body(msg)
|
||||
|
||||
def test_push_public_waiting(self):
|
||||
files = list(self.version.files.values_list('id', flat=True))
|
||||
self.get_app().update(make_public=amo.PUBLIC_WAIT)
|
||||
self.post({
|
||||
'action': 'public',
|
||||
'operating_systems': '',
|
||||
'applications': '',
|
||||
'comments': 'something',
|
||||
'addon_files': files,
|
||||
})
|
||||
eq_(self.get_app().status, amo.STATUS_PUBLIC_WAITING)
|
||||
self._check_log(amo.LOG.APPROVE_VERSION_WAITING)
|
||||
|
||||
eq_(len(mail.outbox), 1)
|
||||
msg = mail.outbox[0]
|
||||
self._check_email(msg, 'App Approved but waiting')
|
||||
self._check_email_body(msg)
|
||||
|
||||
def test_comment(self):
|
||||
self.post({'action': 'comment', 'comments': 'mmm, nice app'})
|
||||
eq_(len(mail.outbox), 0)
|
||||
|
@ -204,7 +222,8 @@ class TestCannedResponses(EditorTest):
|
|||
# choices is grouped by the sort_group, where choices[0] is the
|
||||
# default "Choose a response..." option.
|
||||
# Within that, it's paired by [group, [[response, name],...]].
|
||||
# So above, choices[1][1] gets the first real group's list of responses.
|
||||
# So above, choices[1][1] gets the first real group's list of
|
||||
# responses.
|
||||
eq_(len(choices), 1)
|
||||
assert self.cr_app.response in choices[0]
|
||||
assert self.cr_addon.response not in choices[0]
|
||||
|
|
|
@ -56,7 +56,6 @@ class WebappQueueTable(tables.ModelTable, ItemStateTable):
|
|||
|
||||
return ''.join(o)
|
||||
|
||||
|
||||
def render_created(self, row):
|
||||
return timesince(row.created)
|
||||
|
||||
|
@ -179,11 +178,28 @@ class ReviewApp(ReviewBase):
|
|||
self.files = self.version.files.all()
|
||||
|
||||
def process_public(self):
|
||||
if self.addon.make_public == amo.PUBLIC_IMMEDIATELY:
|
||||
return self.process_public_immediately()
|
||||
return self.process_public_waiting()
|
||||
|
||||
def process_public_waiting(self):
|
||||
"""Make an app pending."""
|
||||
self.set_files(amo.STATUS_PUBLIC_WAITING, self.version.files.all())
|
||||
self.set_addon(highest_status=amo.STATUS_PUBLIC_WAITING,
|
||||
status=amo.STATUS_PUBLIC_WAITING)
|
||||
|
||||
self.log_action(amo.LOG.APPROVE_VERSION_WAITING)
|
||||
self.notify_email('%s_to_public_waiting' % self.review_type,
|
||||
u'App Approved but waiting: %s')
|
||||
|
||||
log.info(u'Making %s public but pending' % self.addon)
|
||||
log.info(u'Sending email for %s' % self.addon)
|
||||
|
||||
def process_public_immediately(self):
|
||||
"""Approve an app."""
|
||||
# Save files first, because set_addon checks to make sure there
|
||||
# is at least one public file or it won't make the addon public.
|
||||
self.set_files(amo.STATUS_PUBLIC, self.version.files.all(),
|
||||
copy_to_mirror=True)
|
||||
self.set_files(amo.STATUS_PUBLIC, self.version.files.all())
|
||||
self.set_addon(highest_status=amo.STATUS_PUBLIC,
|
||||
status=amo.STATUS_PUBLIC)
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ INSTALLED_APPS.remove('search')
|
|||
INSTALLED_APPS = tuple(INSTALLED_APPS)
|
||||
|
||||
INSTALLED_APPS += (
|
||||
'devhub', # Put here so helpers.py doesn't get loaded first.
|
||||
'mkt.site',
|
||||
'mkt.account',
|
||||
'mkt.browse',
|
||||
|
@ -53,7 +54,6 @@ INSTALLED_APPS += (
|
|||
'mkt.submit',
|
||||
'mkt.support',
|
||||
'mkt.webapps',
|
||||
'devhub', # Put here so helpers.py doesn't get loaded first.
|
||||
)
|
||||
|
||||
SUPPORTED_NONLOCALES += (
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django import forms
|
||||
|
||||
import amo
|
||||
from tower import ugettext as _
|
||||
|
||||
|
||||
|
@ -9,7 +10,12 @@ APP_UPSELL_CHOICES = (
|
|||
)
|
||||
|
||||
|
||||
APP_PUBLIC_CHOICES = (
|
||||
(0, _('As soon as it is approved.')),
|
||||
(1, _('Not until I manually make it public.')),
|
||||
)
|
||||
|
||||
|
||||
class AddonChoiceField(forms.ModelChoiceField):
|
||||
def label_from_instance(self, obj):
|
||||
return obj.name
|
||||
|
||||
|
|
|
@ -18,7 +18,8 @@ from translations.fields import TransField
|
|||
|
||||
from mkt.developers.forms import (PaypalSetupForm as OriginalPaypalSetupForm,
|
||||
verify_app_domain)
|
||||
from mkt.site.forms import AddonChoiceField, APP_UPSELL_CHOICES
|
||||
from mkt.site.forms import (AddonChoiceField, APP_UPSELL_CHOICES,
|
||||
APP_PUBLIC_CHOICES)
|
||||
|
||||
|
||||
class DevAgreementForm(happyforms.Form):
|
||||
|
@ -74,7 +75,12 @@ class UpsellForm(happyforms.Form):
|
|||
label=_('App Price'),
|
||||
empty_label=None,
|
||||
required=True)
|
||||
|
||||
make_public = forms.TypedChoiceField(choices=APP_PUBLIC_CHOICES,
|
||||
widget=forms.RadioSelect(),
|
||||
label=_('When should your app be '
|
||||
'made available for sale?'),
|
||||
coerce=int,
|
||||
required=False)
|
||||
do_upsell = forms.TypedChoiceField(coerce=lambda x: bool(int(x)),
|
||||
choices=APP_UPSELL_CHOICES,
|
||||
widget=forms.RadioSelect(),
|
||||
|
@ -98,6 +104,7 @@ class UpsellForm(happyforms.Form):
|
|||
if 'initial' not in kw:
|
||||
kw['initial'] = {}
|
||||
|
||||
kw['initial']['make_public'] = amo.PUBLIC_IMMEDIATELY
|
||||
if self.addon.premium:
|
||||
kw['initial']['price'] = self.addon.premium.price
|
||||
|
||||
|
@ -120,6 +127,10 @@ class UpsellForm(happyforms.Form):
|
|||
raise_required()
|
||||
return self.cleaned_data['free']
|
||||
|
||||
def clean_make_public(self):
|
||||
return (amo.PUBLIC_WAIT if self.cleaned_data.get('make_public')
|
||||
else None)
|
||||
|
||||
def save(self):
|
||||
if 'price' in self.cleaned_data:
|
||||
premium = self.addon.premium
|
||||
|
@ -145,6 +156,8 @@ class UpsellForm(happyforms.Form):
|
|||
elif not self.cleaned_data['do_upsell'] and upsell:
|
||||
upsell.delete()
|
||||
|
||||
self.addon.update(make_public=self.cleaned_data['make_public'])
|
||||
|
||||
|
||||
class AppDetailsBasicForm(AddonFormBasic):
|
||||
"""Form for "Details" submission step."""
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
<a href="https://developer.mozilla.org/en/Apps/Marketplace_Payments#Price_tiers">
|
||||
Learn more about price tiers</a></p>
|
||||
{{ form_field(form.price) }}
|
||||
{{ form_field(form.make_public) }}
|
||||
{% if form.fields['free'].queryset.count() %}
|
||||
<p>{{ loc('Linking this app with its free counterpart allows you
|
||||
to promote your premium app next to the free version.
|
||||
|
|
|
@ -818,6 +818,22 @@ class TestPayments(TestSubmit):
|
|||
AddonUser.objects.create(addon=free, user=self.user)
|
||||
return free
|
||||
|
||||
def test_immediate(self):
|
||||
self.webapp.update(premium_type=amo.ADDON_PREMIUM)
|
||||
res = self.client.post(self.get_url('payments.upsell'),
|
||||
{'price': self.price.pk,
|
||||
'make_public': 0})
|
||||
eq_(res.status_code, 302)
|
||||
eq_(self.get_webapp().make_public, amo.PUBLIC_IMMEDIATELY)
|
||||
|
||||
def test_wait(self):
|
||||
self.webapp.update(premium_type=amo.ADDON_PREMIUM)
|
||||
res = self.client.post(self.get_url('payments.upsell'),
|
||||
{'price': self.price.pk,
|
||||
'make_public': 1})
|
||||
eq_(res.status_code, 302)
|
||||
eq_(self.get_webapp().make_public, amo.PUBLIC_WAIT)
|
||||
|
||||
def test_upsell_states(self):
|
||||
free = self._make_upsell()
|
||||
free.update(status=amo.STATUS_NULL)
|
||||
|
@ -850,7 +866,7 @@ class TestPayments(TestSubmit):
|
|||
res = self.client.get(self.get_url('payments.upsell'),
|
||||
{'price': self.price.pk})
|
||||
eq_(res.status_code, 200)
|
||||
eq_(len(pq(res.content)('div.brform')), 2)
|
||||
eq_(len(pq(res.content)('div.brform')), 3)
|
||||
|
||||
def test_upsell_missing(self):
|
||||
free = Addon.objects.create(type=amo.ADDON_WEBAPP)
|
||||
|
|
Загрузка…
Ссылка в новой задаче