add in a wizard from ashort (bug 677535, 677536)

This commit is contained in:
Andy McKay 2011-09-07 14:34:01 -07:00
Родитель 2ebc572e50
Коммит bfee4f93b6
13 изменённых файлов: 237 добавлений и 86 удалений

Просмотреть файл

@ -963,12 +963,16 @@ class Addon(amo.models.OnChangeMixin, amo.models.ModelBase):
except IndexError:
pass
def has_purchased(self, user):
return (self.is_premium() and
self.addonpurchase_set.filter(user=user).exists())
def can_review(self, user):
return not self.is_premium() or self.has_purchased(user)
@amo.cached_property
def upsold(self):
"""
Return what this is going to upsold from,
or None if there isn't one.
"""
try:
return self._upsell_to.all()[0]
except IndexError:
pass
@property
def all_dependencies(self):

Просмотреть файл

@ -24,7 +24,7 @@ from amo.widgets import EmailWidget
from applications.models import Application, AppVersion
from files.models import File, FileUpload, Platform
from files.utils import parse_addon, VERSION_RE
from market.models import Price
from market.models import AddonPremium, Price
from translations.widgets import TranslationTextarea, TranslationTextInput
from translations.fields import TransTextarea, TransField
from translations.models import delete_translation
@ -840,14 +840,18 @@ class PremiumForm(happyforms.Form):
text = forms.CharField(widget=forms.Textarea(), required=False)
def __init__(self, *args, **kw):
self.addon = kw.pop('addon')
self.amo_user = kw.pop('amo_user')
self.extra = kw.pop('extra')
self.addon = self.extra['addon']
kw['initial'] = {
'paypal_id': self.addon.paypal_id,
'price': self.addon.addonpremium.price,
'do_upsell': 0,
}
upsell = self.get_upsell_to()
try:
kw['initial']['price'] = self.addon.addonpremium.price
except AddonPremium.DoesNotExist:
pass
upsell = self.addon.upsold
if upsell:
kw['initial'].update({
'text': upsell.text,
@ -856,11 +860,18 @@ class PremiumForm(happyforms.Form):
})
super(PremiumForm, self).__init__(*args, **kw)
self.fields['free'].queryset = (self.amo_user.addons
self.fields['free'].queryset = (self.extra['amo_user'].addons
.exclude(pk=self.addon.pk))
# For the wizard, we need fields not shown to be optional
for field in self.extra.get('not_required', []):
self.fields[field].required = False
def clean_paypal_id(self):
token = self.addon.addonpremium.paypal_permissions_token
try:
token = self.addon.addonpremium.paypal_permissions_token
except AddonPremium.DoesNotExist:
raise forms.ValidationError(_('No third party token set up.'))
if not paypal.check_refund_permission(token):
raise forms.ValidationError(_('Third party refund not set up.'))
return self.cleaned_data['paypal_id']
@ -875,21 +886,20 @@ class PremiumForm(happyforms.Form):
raise forms.ValidationError(_('This field is required.'))
return self.cleaned_data['free']
def get_upsell_to(self):
"""
If you use addon.upsell you get the free to premium upsell.
This is the other, premium to free for the purposes of editing.
"""
try:
return self.addon._upsell_to.all()[0]
except IndexError:
pass
def save(self):
self.addon.update(paypal_id=self.cleaned_data['paypal_id'])
self.addon.addonpremium.update(price=self.cleaned_data['price'])
if self.cleaned_data['paypal_id']:
self.addon.update(paypal_id=self.cleaned_data['paypal_id'])
upsell = self.get_upsell_to()
if self.cleaned_data['price']:
try:
premium = self.addon.addonpremium
except AddonPremium.DoesNotExist:
premium = AddonPremium()
premium.addon = self.addon
premium.price = self.cleaned_data['price']
premium.save()
upsell = self.addon.upsold
if (self.cleaned_data['do_upsell'] and
self.cleaned_data['text'] and self.cleaned_data['free']):

Просмотреть файл

@ -357,6 +357,7 @@ class LegacyAddonLog(models.Model):
class SubmitStep(models.Model):
addon = models.ForeignKey(Addon)
step = models.IntegerField()
type = models.CharField(max_length=20, default='submit')
class Meta:
db_table = 'submit_step'

Просмотреть файл

@ -0,0 +1,18 @@
<div id="org-dev" class="brform paypal">
<label for="id_paypal_id">{{ _('What is your PayPal ID?') }}</label>
<div class="extra">
{% trans %}You must have PayPal configured to allow third party refunds
permissions from this account.{% endtrans %}
{% if not premium.paypal_permissions_token %}
<strong>
{% trans %}You do not have refund permission setup,
<a href="{{ paypal_url }}">click here to set this up on Paypal</a>.
{% endtrans %}
</strong>
{% else %}
{% trans %}You have the refund permission setup.{% endtrans %}
{% endif %}
</div>
{{ form.paypal_id.errors }}
<div>{{ form.paypal_id }}</div>
</div>

Просмотреть файл

@ -0,0 +1,13 @@
<div class="brform">
<label for="id_price">{{ form.price.label }}</label>
<div class="extra">
{% trans doc_url=url('devhub.docs', doc_name='marketplace') %}
Select a price tier for your add-on below. Price tiers
are based on US Dollars and are fixed for all supported countries. For more
information, <a href="{{ doc_url }}">view our pricing matrix</a>.
{% endtrans %}
</div>
{{ form.price.errors }}
<div>{{ form.price }}</div>
</div>

Просмотреть файл

@ -0,0 +1,20 @@
{% from 'devhub/includes/macros.html' import some_html_tip %}
<div class="brform">
{{ form.do_upsell.errors }}
<div>{{ form.do_upsell }}</div>
<div class="indent">
{{ form.free.errors }}
<div>{{ form.free }}</div>
<div class="extra">
{% trans %}
Pitch your premium add-on, describing the added benefits
and functionality over the free version. This will appear
on the free add-on's details page.
{% endtrans %}
</div>
{{ form.text.errors }}
<div>{{ form.text }}</div>
{{ some_html_tip() }}
</div>
</div>

Просмотреть файл

@ -15,7 +15,8 @@
<p>{% trans %}If this is the free version of your add-on, please create a new UUID
for your premium add-on and submit it separately before enrolling in Mozilla Marketplace.
{% endtrans %}</p>
<button id="marketplace-submit" type="submit">{{ _('Sell this Add-on') }}</button>
<a class="button" href="{{ url('devhub.market.1', addon.slug) }}"
id="marketplace-submit" type="submit">{{ _('Sell this Add-on') }}</a>
{% trans %}
or <a id="marketplace-cancel" href="#">Cancel</a>
{% endtrans %}

Просмотреть файл

@ -1,5 +1,4 @@
{% extends "devhub/base.html" %}
{% from 'devhub/includes/macros.html' import some_html_tip %}
{% set title = _('Manage Payments') %}
{% block title %}{{ dev_page_title(title, addon) }}{% endblock %}
@ -18,54 +17,9 @@
<section class="primary payments devhub-form" role="main">
<form method="post" action="{{ url('devhub.addons.payments', addon.slug) }}">
{{ csrf() }}
<div id="org-dev" class="brform paypal">
<label for="id_paypal_id">{{ _('What is your PayPal ID?') }}</label>
<div class="extra">
{% trans %}You must have PayPal configured to allow third party refunds
permissions from this account.{% endtrans %}
{% if not premium.paypal_permissions_token %}
<strong>
{% trans %}You do not have refund permission setup,
<a href="{{ paypal_url }}">click here to set this up on Paypal</a>.
{% endtrans %}
</strong>
{% else %}
{% trans %}You have the refund permission setup.{% endtrans %}
{% endif %}
</div>
{{ form.paypal_id.errors }}
<div>{{ form.paypal_id }}</div>
</div>
<div class="brform">
<label for="id_price">{{ form.price.label }}</label>
<div class="extra">
{% trans doc_url=url('devhub.docs', doc_name='marketplace') %}
Select a price tier for your add-on below. Price tiers
are based on US Dollars and are fixed for all supported countries. For more
information, <a href="{{ doc_url }}">view our pricing matrix</a>.
{% endtrans %}
</div>
{{ form.price.errors }}
<div>{{ form.price }}</div>
</div>
<div class="brform">
{{ form.do_upsell.errors }}
<div>{{ form.do_upsell }}</div>
<div class="indent">
{{ form.free.errors }}
<div>{{ form.free }}</div>
<div class="extra">
{% trans %}
Pitch your premium add-on, describing the added benefits
and functionality over the free version. This will appear
on the free add-on's details page.
{% endtrans %}
</div>
{{ form.text.errors }}
<div>{{ form.text }}</div>
{{ some_html_tip() }}
</div>
</div>
{% include "devhub/payments/includes/paypal.html" %}
{% include "devhub/payments/includes/tier.html" %}
{% include "devhub/payments/includes/upsell.html" %}
<button type="submit">{{ _('Save Changes') }}</button>
</form>
</section>

Просмотреть файл

@ -858,6 +858,61 @@ class TestMarketplace(amo.tests.TestCase):
self.addon = Addon.objects.get(pk=self.addon.pk)
eq_(self.addon.addonpremium.paypal_permissions_token, 'FOO')
@mock.patch('paypal.get_permissions_token', lambda x, y: x.upper())
def test_permissions_token_no_premium(self):
self.setup_premium()
# They could hit this URL before anything else, we need to cope
# with AddonPremium not being there.
self.addon.addonpremium.delete()
url = reverse('devhub.addons.acquire_refund_permission',
args=[self.addon.slug])
data = {'request_token': 'foo', 'verification_code': 'bar'}
self.client.get('%s?%s' % (url, urlencode(data)))
self.addon = Addon.objects.get(pk=self.addon.pk)
eq_(self.addon.addonpremium.paypal_permissions_token, 'FOO')
@mock.patch('devhub.forms.PremiumForm.clean_paypal_id')
def test_wizard_step_1(self, clean_paypal_id):
url = reverse('devhub.market.1', args=[self.addon.slug])
data = 'some@paypal.com'
clean_paypal_id.return_value = data
eq_(self.client.post(url, {'paypal_id': data}).status_code, 302)
eq_(Addon.objects.get(pk=self.addon.pk).paypal_id, data)
def test_wizard_step_2(self):
self.price = Price.objects.create(price='0.99')
url = reverse('devhub.market.2', args=[self.addon.slug])
eq_(self.client.post(url, {'price': self.price.pk}).status_code, 302)
eq_(Addon.objects.get(pk=self.addon.pk).premium.price.pk,
self.price.pk)
def test_wizard_step_3(self):
url = reverse('devhub.market.3', args=[self.addon.slug])
self.other_addon = Addon.objects.create(type=amo.ADDON_EXTENSION)
AddonUser.objects.create(addon=self.other_addon,
user=self.addon.authors.all()[0])
data = {
'free': self.other_addon.pk,
'do_upsell': 1,
'text': 'some upsell'
}
eq_(self.client.post(url, data).status_code, 302)
eq_(Addon.objects.get(pk=self.addon.pk).upsold.free,
self.other_addon)
def test_wizard_step_4_failed(self):
url = reverse('devhub.market.4', args=[self.addon.slug])
assert not Addon.objects.get(pk=self.addon.pk).is_premium()
eq_(self.client.post(url, {}).status_code, 302)
assert not Addon.objects.get(pk=self.addon.pk).is_premium()
def test_wizard_step_4(self):
url = reverse('devhub.market.4', args=[self.addon.slug])
assert not Addon.objects.get(pk=self.addon.pk).is_premium()
self.setup_premium()
eq_(self.client.post(url, {}).status_code, 302)
assert Addon.objects.get(pk=self.addon.pk).is_premium()
class TestDelete(amo.tests.TestCase):
fixtures = ('base/apps', 'base/users', 'base/addon_3615',

Просмотреть файл

@ -20,6 +20,13 @@ submit_patterns = patterns('',
url('^bump$', views.submit_bump, name='devhub.submit.bump'),
)
marketplace_patterns = patterns('',
url('^1$', views.marketplace_paypal, name='devhub.market.1'),
url('^2$', views.marketplace_pricing, name='devhub.market.2'),
url('^3$', views.marketplace_upsell, name='devhub.market.3'),
url('^4$', views.marketplace_confirm, name='devhub.market.4')
)
# These will all start with /addon/<addon_id>/
detail_patterns = patterns('',
# Redirect to the edit page from the base.
@ -37,6 +44,7 @@ detail_patterns = patterns('',
name='devhub.addons.payments.disable'),
url('^payments/permission/refund$', views.acquire_refund_permission,
name='devhub.addons.acquire_refund_permission'),
url('^payments/', include(marketplace_patterns)),
url('^profile$', views.profile, name='devhub.addons.profile'),
url('^profile/remove$', views.remove_profile,
name='devhub.addons.profile.remove'),

Просмотреть файл

@ -38,7 +38,7 @@ from amo.urlresolvers import reverse
from access import acl
from addons import forms as addon_forms
from addons.decorators import addon_view
from addons.models import Addon, AddonUser
from addons.models import Addon, AddonPremium, AddonUser
from addons.views import BaseFilter
from devhub.decorators import dev_required
from devhub.forms import CheckCompatibilityForm
@ -47,6 +47,7 @@ from devhub import perf
from editors.helpers import get_position
from files.models import File, FileUpload, Platform
from files.utils import parse_addon
from market.models import Price
import paypal
from translations.models import Translation, delete_translation
from users.models import UserProfile
@ -401,7 +402,8 @@ def payments(request, addon_id, addon):
def _premium(request, addon_id, addon):
premium_form = forms.PremiumForm(request.POST or None,
addon=addon, amo_user=request.amo_user)
extra={'addon': addon,
'amo_user': request.amo_user})
if request.method == 'POST' and premium_form.is_valid():
premium_form.save()
messages.success(request, _('Changes successfully saved.'))
@ -418,6 +420,8 @@ def _premium(request, addon_id, addon):
# Which is a real pain for our dev servers.
if 'malformed' in e.message:
log.debug('PayPal does not like your server, set to empty.')
else:
raise
return jingo.render(request, 'devhub/payments/premium.html',
dict(addon=addon, premium=addon.addonpremium,
@ -473,13 +477,15 @@ def _save_charity(addon, contrib_form, charity_form):
def acquire_refund_permission(request, addon_id, addon):
"""This is the callback from Paypal."""
log.debug('User approved refund for addon: %s' % addon_id)
print paypal.get_permissions_token('b','a')
print paypal.get_permissions_token
token = paypal.get_permissions_token(request.GET['request_token'],
request.GET['verification_code'])
log.debug('Got refund token for addon: %s' % addon_id)
# Sadly this is an update on a POST.
addon.addonpremium.update(paypal_permissions_token=token)
try:
addon.addonpremium.update(paypal_permissions_token=token)
except AddonPremium.DoesNotExist:
AddonPremium.objects.create(addon=addon,
paypal_permissions_token=token)
amo.log(amo.LOG.EDIT_PROPERTIES, addon)
return redirect('devhub.addons.payments', addon.slug)
@ -1227,7 +1233,7 @@ def submit_step(step):
def decorator(f):
@functools.wraps(f)
def wrapper(request, *args, **kw):
max_step = 7
max_step =7
# We only bounce on pages with an addon id.
if 'addon' in kw:
addon = kw['addon']
@ -1236,8 +1242,7 @@ def submit_step(step):
max_step = on_step[0].step
if max_step < step:
# The step was too high, so bounce to the saved step.
return redirect('devhub.submit.%s' % max_step,
addon.slug)
return redirect('devhub.submit.7', addon.slug)
elif step != max_step:
# We couldn't find a step, so we must be done.
return redirect('devhub.submit.7', addon.slug)
@ -1250,6 +1255,67 @@ def submit_step(step):
return decorator
@dev_required
def marketplace_paypal(request, addon_id, addon):
"""
Start of the marketplace wizard, none of this means anything until
addon-premium is set, so we'll just save as we go along. Further
we might have the PayPal permissions bounce happen at any time
so we'll need to cope with AddonPremium being incomplete.
"""
form = forms.PremiumForm(request.POST or None,
extra={'addon': addon,
'amo_user': request.amo_user,
'not_required': ['price']})
if form.is_valid():
form.save()
return redirect('devhub.market.2', addon.slug)
return jingo.render(request, 'devhub/payments/paypal.html',
{'form': form, 'addon': addon, 'premium': None})
@dev_required
def marketplace_pricing(request, addon_id, addon):
form = forms.PremiumForm(request.POST or None,
extra={'addon': addon,
'amo_user': request.amo_user,
'not_required': ['paypal_id']})
if form.is_valid():
form.save()
return redirect('devhub.market.3', addon.slug)
return jingo.render(request, 'devhub/payments/tier.html',
{'form': form, 'addon': addon, 'premium': None})
@dev_required
def marketplace_upsell(request, addon_id, addon):
form = forms.PremiumForm(request.POST or None,
extra={'addon': addon,
'amo_user': request.amo_user,
'not_required': ['price', 'paypal_id']})
if form.is_valid():
form.save()
return redirect('devhub.market.4', addon.slug)
return jingo.render(request, 'devhub/payments/upsell.html',
{'form': form, 'addon': addon, 'premium': None})
@dev_required
def marketplace_confirm(request, addon_id, addon):
if request.method == 'POST':
# Minimum required to become premium.
if (not addon.paypal_id or addon.addonpremium.price
or addon.addonpremium.paypal_permissions_token):
messages.error(request, 'Some required details are missing.')
return redirect('devhub.market.1', addon.slug)
addon.update(premium_type=amo.ADDON_PREMIUM)
return redirect('devhub.addons.payments', addon.slug)
return jingo.render(request, 'devhub/payments/confirm.html',
{'addon': addon, 'upsell': addon.upsold,
'premium': addon.premium})
@login_required
@submit_step(1)
def submit(request, step, webapp=False):
@ -1285,7 +1351,7 @@ def submit_addon(request, step, webapp=False):
addon = Addon.from_upload(data['upload'], p)
AddonUser(addon=addon, user=request.amo_user).save()
SubmitStep.objects.create(addon=addon, step=3)
SubmitStep.objects.create(addon=addon, step=3, type='submit')
return redirect('devhub.submit.3', addon.slug)
template = 'upload_webapp.html' if webapp else 'upload.html'
return jingo.render(request, 'devhub/addons/submit/%s' % template,

Просмотреть файл

@ -97,7 +97,7 @@ def create_addon_purchase(sender, instance, **kw):
class AddonPremium(amo.models.ModelBase):
"""Additions to the Addon model that only apply to Premium add-ons."""
addon = models.OneToOneField('addons.Addon')
price = models.ForeignKey(Price)
price = models.ForeignKey(Price, blank=True, null=True)
paypal_permissions_token = models.CharField(max_length=255, blank=True)
class Meta:

Просмотреть файл

@ -0,0 +1 @@
ALTER TABLE addons_premium MODIFY price_id int(11) NULL;