addons-server/mkt/submit/forms.py

398 строки
15 KiB
Python

import datetime
from django import forms
import happyforms
import waffle
from tower import ugettext as _, ugettext_lazy as _lazy
import amo
from addons.models import Addon, AddonUpsell, BlacklistedSlug
from amo.utils import slug_validator
from apps.users.models import UserNotification
from apps.users.notifications import app_surveys
from editors.models import RereviewQueue
from files.models import FileUpload
from files.utils import parse_addon
from market.models import AddonPremium, Price
from translations.fields import TransField
from translations.forms import TranslationFormMixin
from translations.widgets import TransInput, TransTextarea
from mkt.constants import FREE_PLATFORMS, PAID_PLATFORMS
from mkt.site.forms import AddonChoiceField, APP_PUBLIC_CHOICES
def mark_for_rereview(addon, added_devices, removed_devices):
msg = _(u'Device(s) changed: {0}').format(', '.join(
[_(u'Added {0}').format(unicode(amo.DEVICE_TYPES[d].name))
for d in added_devices] +
[_(u'Removed {0}').format(unicode(amo.DEVICE_TYPES[d].name))
for d in removed_devices]))
RereviewQueue.flag(addon, amo.LOG.REREVIEW_DEVICES_ADDED, msg)
class DeviceTypeForm(happyforms.Form):
ERRORS = {
'both': _lazy(u'Cannot be free and paid.'),
'none': _lazy(u'Please select a device.'),
'packaged': _lazy(u'Packaged apps are valid for only Firefox OS.'),
}
free_platforms = forms.MultipleChoiceField(
choices=FREE_PLATFORMS, required=False)
paid_platforms = forms.MultipleChoiceField(
choices=PAID_PLATFORMS, required=False)
def save(self, addon, is_paid):
data = self.cleaned_data[
'paid_platforms' if is_paid else 'free_platforms']
submitted_data = self.get_devices(t.split('-', 1)[1] for t in data)
new_types = set(dev.id for dev in submitted_data)
old_types = set(amo.DEVICE_TYPES[x.id].id for x in addon.device_types)
added_devices = new_types - old_types
removed_devices = old_types - new_types
for d in added_devices:
addon.addondevicetype_set.create(device_type=d)
for d in removed_devices:
addon.addondevicetype_set.filter(device_type=d).delete()
# Send app to re-review queue if public and new devices are added.
if added_devices and addon.status == amo.STATUS_PUBLIC:
mark_for_rereview(addon, added_devices, removed_devices)
def _add_error(self, msg):
self._errors['free_platforms'] = self._errors['paid_platforms'] = (
self.ERRORS[msg])
def _get_combined(self):
devices = (self.cleaned_data.get('free_platforms', []) +
self.cleaned_data.get('paid_platforms', []))
return set(d.split('-', 1)[1] for d in devices)
def clean(self):
data = self.cleaned_data
paid = data.get('paid_platforms', [])
free = data.get('free_platforms', [])
# Check that they didn't select both.
if free and paid:
self._add_error('both')
return data
# Check that they selected one.
if not free and not paid:
self._add_error('none')
return data
return super(DeviceTypeForm, self).clean()
def get_devices(self, source=None):
"""Returns a device based on the requested free or paid."""
if source is None:
source = self._get_combined()
platforms = {'firefoxos': amo.DEVICE_GAIA,
'desktop': amo.DEVICE_DESKTOP,
'android-mobile': amo.DEVICE_MOBILE,
'android-tablet': amo.DEVICE_TABLET}
return map(platforms.get, source)
def is_paid(self):
return bool(self.cleaned_data.get('paid_platforms', False))
def get_paid(self):
"""Returns the premium type. Should not be used if the form is used to
modify an existing app.
"""
return amo.ADDON_PREMIUM if self.is_paid() else amo.ADDON_FREE
class DevAgreementForm(happyforms.Form):
read_dev_agreement = forms.BooleanField(label=_lazy(u'Agree and Continue'),
widget=forms.HiddenInput)
newsletter = forms.BooleanField(required=False, label=app_surveys.label,
widget=forms.CheckboxInput)
def __init__(self, *args, **kw):
self.instance = kw.pop('instance')
super(DevAgreementForm, self).__init__(*args, **kw)
def save(self):
self.instance.read_dev_agreement = datetime.datetime.now()
self.instance.save()
if self.cleaned_data.get('newsletter'):
UserNotification.update_or_create(user=self.instance,
notification_id=app_surveys.id, update={'enabled': True})
class NewWebappVersionForm(happyforms.Form):
upload_error = _lazy(u'There was an error with your upload. '
u'Please try again.')
upload = forms.ModelChoiceField(widget=forms.HiddenInput,
queryset=FileUpload.objects.filter(valid=True),
error_messages={'invalid_choice': upload_error})
def clean(self):
data = self.cleaned_data
if 'upload' not in self.cleaned_data:
self._errors['upload'] = self.upload_error
return
# Packaged apps are only valid for firefox os.
if self.is_packaged():
# Now run the packaged app check, done in clean, because
# clean_packaged needs to be processed first.
try:
pkg = parse_addon(data['upload'], self.addon)
except forms.ValidationError, e:
self._errors['upload'] = self.error_class(e.messages)
return
ver = pkg.get('version')
if (ver and self.addon and
self.addon.versions.filter(version=ver).exists()):
self._errors['upload'] = _(u'Version %s already exists') % ver
return
else:
# Throw an error if this is a dupe.
# (JS sets manifest as `upload.name`.)
try:
from mkt.developers.forms import verify_app_domain
verify_app_domain(data['upload'].name)
except forms.ValidationError, e:
self._errors['upload'] = self.error_class(e.messages)
return
return data
def is_packaged(self):
return self._is_packaged
def __init__(self, *args, **kw):
self.addon = kw.pop('addon', None)
self._is_packaged = kw.pop('is_packaged', False)
super(NewWebappVersionForm, self).__init__(*args, **kw)
if (not waffle.switch_is_active('allow-b2g-paid-submission')
and 'paid_platforms' in self.fields):
del self.fields['paid_platforms']
class NewWebappForm(DeviceTypeForm, NewWebappVersionForm):
upload = forms.ModelChoiceField(widget=forms.HiddenInput,
queryset=FileUpload.objects.filter(valid=True),
error_messages={'invalid_choice': _lazy(
u'There was an error with your upload. Please try again.')})
packaged = forms.BooleanField(required=False)
def _add_error(self, msg):
self._errors['free_platforms'] = self._errors['paid_platforms'] = (
self.ERRORS[msg])
def clean(self):
data = super(NewWebappForm, self).clean()
if not data:
return
if self.is_packaged() and 'firefoxos' not in self._get_combined():
self._errors['free_platforms'] = self._errors['paid_platforms'] = (
self.ERRORS['packaged'])
return
return data
def is_packaged(self):
return self._is_packaged or self.cleaned_data.get('packaged', False)
class PaypalSetupForm(happyforms.Form):
business_account = forms.ChoiceField(widget=forms.RadioSelect, choices=[],
label=_lazy(u'Do you already have a PayPal Premier '
'or Business account?'))
email = forms.EmailField(required=False,
label=_lazy(u'PayPal email address'))
def __init__(self, *args, **kw):
super(PaypalSetupForm, self).__init__(*args, **kw)
self.fields['business_account'].choices = (('yes', _lazy('Yes')),
('no', _lazy('No')),
('later', _lazy(u"I'll link my PayPal account later.")))
def clean(self):
data = self.cleaned_data
if data.get('business_account') == 'yes' and not data.get('email'):
msg = _(u'The PayPal email is required.')
self._errors['email'] = self.error_class([msg])
return data
class UpsellForm(happyforms.Form):
price = forms.ModelChoiceField(queryset=Price.objects.active(),
label=_lazy(u'App Price'),
empty_label=None,
required=True)
make_public = forms.TypedChoiceField(choices=APP_PUBLIC_CHOICES,
widget=forms.RadioSelect(),
label=_lazy(u'When should your app be '
'made available for sale?'),
coerce=int,
required=False)
free = AddonChoiceField(queryset=Addon.objects.none(),
required=False,
empty_label='',
label=_lazy(u'App to upgrade from'),
widget=forms.Select())
def __init__(self, *args, **kw):
self.extra = kw.pop('extra')
self.request = kw.pop('request')
self.addon = self.extra['addon']
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
super(UpsellForm, self).__init__(*args, **kw)
self.fields['free'].queryset = (self.extra['amo_user'].addons
.exclude(pk=self.addon.pk)
.filter(premium_type__in=amo.ADDON_FREES,
status__in=amo.VALID_STATUSES,
type=self.addon.type))
if len(self.fields['price'].choices) > 1:
# Tier 0 (Free) should not be the default selection.
self.initial['price'] = (Price.objects.active()
.exclude(price='0.00')[0])
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
if not premium:
premium = AddonPremium()
premium.addon = self.addon
premium.price = self.cleaned_data['price']
premium.save()
upsell = self.addon.upsold
if self.cleaned_data['free']:
# Check if this app was already a premium version for another app.
if upsell and upsell.free != self.cleaned_data['free']:
upsell.delete()
if not upsell:
upsell = AddonUpsell(premium=self.addon)
upsell.free = self.cleaned_data['free']
upsell.save()
elif upsell:
upsell.delete()
self.addon.update(make_public=self.cleaned_data['make_public'])
class AppDetailsBasicForm(TranslationFormMixin, happyforms.ModelForm):
"""Form for "Details" submission step."""
app_slug = forms.CharField(max_length=30,
widget=forms.TextInput(attrs={'class': 'm'}))
summary = TransField(max_length=1024,
label=_lazy(u"Brief Summary:"),
help_text=_lazy(u'This summary will be shown in listings and '
'searches.'),
widget=TransTextarea(attrs={'rows': 2, 'class': 'full'}))
description = TransField(required=False,
label=_lazy(u'Additional Information:'),
help_text=_lazy(u'This description will appear on the details page.'),
widget=TransTextarea(attrs={'rows': 4}))
privacy_policy = TransField(widget=TransTextarea(attrs={'rows': 6}),
label=_lazy(u'Privacy Policy:'),
help_text=_lazy(u"A privacy policy that explains what "
"data is transmitted from a user's computer and how "
"it is used is required."))
homepage = TransField.adapt(forms.URLField)(required=False,
verify_exists=False, label=_lazy(u'Homepage:'),
help_text=_lazy(u'If your app has another homepage, enter its address '
'here.'),
widget=TransInput(attrs={'class': 'full'}))
support_url = TransField.adapt(forms.URLField)(required=False,
verify_exists=False, label=_lazy(u'Support Website:'),
help_text=_lazy(u'If your app has a support website or forum, enter '
'its address here.'),
widget=TransInput(attrs={'class': 'full'}))
support_email = TransField.adapt(forms.EmailField)(
label=_lazy(u'Support Email:'),
help_text=_lazy(u'The email address used by end users to contact you '
'with support issues and refund requests.'),
widget=TransInput(attrs={'class': 'full'}))
flash = forms.TypedChoiceField(required=False,
coerce=lambda x: bool(int(x)),
label=_lazy(u'Does your app require Flash support?'),
initial=0,
choices=(
(1, _lazy(u'Yes')),
(0, _lazy(u'No')),
),
widget=forms.RadioSelect)
publish = forms.BooleanField(required=False, initial=1,
label=_lazy(u"Publish my app in the Firefox Marketplace as soon as "
"it's reviewed."),
help_text=_lazy(u"If selected your app will be published immediately "
"following its approval by reviewers. If you don't "
"select this option you will be notified via email "
"about your app's approval and you will need to log "
"in and manually publish it."))
class Meta:
model = Addon
fields = ('app_slug', 'summary', 'description', 'privacy_policy',
'homepage', 'support_url', 'support_email')
def __init__(self, *args, **kw):
self.request = kw.pop('request')
kw.setdefault('initial', {})
# Prefill support email.
locale = self.base_fields['support_email'].default_locale.lower()
kw['initial']['support_email'] = {locale: self.request.amo_user.email}
super(AppDetailsBasicForm, self).__init__(*args, **kw)
def clean_app_slug(self):
slug_field = 'app_slug'
target = self.cleaned_data[slug_field]
slug_validator(target, lower=False)
if target != getattr(self.instance, slug_field):
if Addon.objects.filter(**{slug_field: target}).exists():
raise forms.ValidationError(_('This slug is already in use.'))
if BlacklistedSlug.blocked(target):
raise forms.ValidationError(
_('The slug cannot be %s.' % target))
return target
def save(self, *args, **kw):
uses_flash = self.cleaned_data.get('flash')
af = self.instance.get_latest_file()
if af is not None:
af.update(uses_flash=bool(uses_flash))
form = super(AppDetailsBasicForm, self).save(commit=False)
form.save()
return form