class PolicyForm(TranslationFormMixin, AMOModelForm): """Form for editing the add-ons EULA and privacy policy.""" has_eula = forms.BooleanField( required=False, label=_lazy(u'This add-on has an End User License Agreement')) eula = TransField(widget=TransTextarea(), required=False, label=_lazy(u"Please specify your add-on's " "End User License Agreement:")) has_priv = forms.BooleanField( required=False, label=_lazy(u"This add-on has a Privacy Policy")) privacy_policy = TransField( widget=TransTextarea(), required=False, label=_lazy(u"Please specify your add-on's Privacy Policy:")) class Meta: model = Addon fields = ('eula', 'privacy_policy') def save(self, commit=True): super(PolicyForm, self).save(commit) for k, field in (('has_eula', 'eula'), ('has_priv', 'privacy_policy')): if not self.cleaned_data[k]: delete_translation(self.instance, field)
class _Form(TranslationFormMixin, happyforms.ModelForm): the_reason = TransField(widget=TransTextarea(), required=fields_required, label=_("Why did you make this add-on?")) the_future = TransField(widget=TransTextarea(), required=fields_required, label=_("What's next for this add-on?")) class Meta: model = Addon fields = ('the_reason', 'the_future')
class _Form(TranslationFormMixin, happyforms.ModelForm): the_reason = TransField(widget=TransTextarea(), required=fields_required, label=the_reason_label) the_future = TransField(widget=TransTextarea(), required=fields_required, label=the_future_label) class Meta: model = Addon fields = ('the_reason', 'the_future')
class AppDetailsBasicForm(AddonFormBasic): """Form for "Details" submission step.""" name = TransField(max_length=128, widget=TransInput(attrs={'class': 'name l'})) slug = forms.CharField(max_length=30, widget=forms.TextInput(attrs={'class': 'm'})) summary = TransField(max_length=250, 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'})) class Meta: model = Addon fields = ('name', 'slug', 'summary', 'tags', 'description', 'privacy_policy', 'homepage', 'support_url', 'support_email')
class AddonFormTechnical(AddonFormBase): developer_comments = TransField(widget=TransTextarea, required=False) class Meta: model = Addon fields = ('developer_comments', 'view_source', 'site_specific', 'external_software', 'auto_repackage', 'public_stats')
class PreviewForm(happyforms.ModelForm): caption = TransField(widget=TransTextarea, required=False) file_upload = forms.FileField(required=False) upload_hash = forms.CharField(required=False) def save(self, addon, commit=True): if self.cleaned_data: self.instance.addon = addon if self.cleaned_data.get('DELETE'): # Existing preview. if self.instance.id: self.instance.delete() # User has no desire to save this preview. return super(PreviewForm, self).save(commit=commit) if self.cleaned_data['upload_hash']: upload_hash = self.cleaned_data['upload_hash'] upload_path = os.path.join(settings.TMP_PATH, 'preview', upload_hash) tasks.resize_preview.delay(upload_path, self.instance, set_modified_on=[self.instance]) class Meta: model = Preview fields = ('caption', 'file_upload', 'upload_hash', 'id', 'position')
class Step3Form(addons.forms.AddonFormBasic): description = TransField(widget=TransTextarea, required=False) class Meta: model = Addon fields = ('name', 'slug', 'summary', 'tags', 'description', 'homepage', 'support_email', 'support_url')
class AppVersionForm(happyforms.ModelForm): releasenotes = TransField(widget=TransTextarea(), required=False) approvalnotes = forms.CharField( widget=TranslationTextarea(attrs={'rows': 4}), required=False) publish_immediately = forms.BooleanField(required=False) class Meta: model = Version fields = ('releasenotes', 'approvalnotes') def __init__(self, *args, **kwargs): super(AppVersionForm, self).__init__(*args, **kwargs) self.fields['publish_immediately'].initial = ( self.instance.addon.make_public == amo.PUBLIC_IMMEDIATELY) def save(self, *args, **kwargs): rval = super(AppVersionForm, self).save(*args, **kwargs) if self.instance.all_files[0].status == amo.STATUS_PENDING: # If version is pending, allow changes to make_public, which lives # on the app itself. if self.cleaned_data.get('publish_immediately'): make_public = amo.PUBLIC_IMMEDIATELY else: make_public = amo.PUBLIC_WAIT self.instance.addon.update(make_public=make_public) return rval
class AppFormDetails(addons.forms.AddonFormBase): default_locale = forms.TypedChoiceField(required=False, choices=Addon.LOCALES) homepage = TransField.adapt(forms.URLField)(required=False) privacy_policy = TransField( widget=TransTextarea(), required=True, label=_lazy(u"Please specify your app's Privacy Policy")) class Meta: model = Addon fields = ('default_locale', 'homepage', 'privacy_policy') def clean(self): # Make sure we have the required translations in the new locale. required = ['name', 'description'] data = self.cleaned_data if not self.errors and 'default_locale' in self.changed_data: fields = dict( (k, getattr(self.instance, k + '_id')) for k in required) locale = data['default_locale'] ids = filter(None, fields.values()) qs = (Translation.objects.filter( locale=locale, id__in=ids, localized_string__isnull=False).values_list('id', flat=True)) missing = [k for k, v in fields.items() if v not in qs] if missing: raise forms.ValidationError( _('Before changing your default locale you must have a ' 'name and description in that locale. ' 'You are missing %s.') % ', '.join(map(repr, missing))) return data
class Step3WebappForm(Step3Form): """Form to override certain fields for webapps""" name = TransField(max_length=128) homepage = TransField.adapt(forms.URLField)(required=False, verify_exists=False) support_url = TransField.adapt(forms.URLField)(required=False, verify_exists=False) support_email = TransField.adapt(forms.EmailField)(required=False)
class VersionForm(happyforms.ModelForm): releasenotes = TransField(widget=TransTextarea(), required=False) approvalnotes = forms.CharField( widget=TranslationTextarea(attrs={'rows': 4}), required=False) class Meta: model = Version fields = ('releasenotes', 'approvalnotes')
class VersionForm(WithSourceMixin, happyforms.ModelForm): releasenotes = TransField(widget=TransTextarea(), required=False) approvalnotes = forms.CharField( widget=TranslationTextarea(attrs={'rows': 4}), required=False) source = forms.FileField(required=False, widget=SourceFileInput) class Meta: model = Version fields = ('releasenotes', 'approvalnotes', 'source')
class PolicyForm(TranslationFormMixin, AMOModelForm): """Form for editing the add-ons EULA and privacy policy.""" has_eula = forms.BooleanField( required=False, label=_lazy(u'This add-on has an End-User License Agreement')) eula = TransField(widget=TransTextarea(), required=False, label=_lazy(u"Please specify your add-on's " "End-User License Agreement:")) has_priv = forms.BooleanField( required=False, label=_lazy(u"This add-on has a Privacy Policy")) privacy_policy = TransField( widget=TransTextarea(), required=False, label=_lazy(u"Please specify your add-on's Privacy Policy:")) def __init__(self, *args, **kw): self.addon = kw.pop('addon', None) if not self.addon: raise ValueError('addon keyword arg cannot be None') kw['instance'] = self.addon kw['initial'] = dict(has_priv=self._has_field('privacy_policy'), has_eula=self._has_field('eula')) super(PolicyForm, self).__init__(*args, **kw) def _has_field(self, name): # If there's a eula in any language, this addon has a eula. n = getattr(self.addon, u'%s_id' % name) return any(map(bool, Translation.objects.filter(id=n))) class Meta: model = Addon fields = ('eula', 'privacy_policy') def save(self, commit=True): ob = super(PolicyForm, self).save(commit) for k, field in (('has_eula', 'eula'), ('has_priv', 'privacy_policy')): if not self.cleaned_data[k]: delete_translation(self.instance, field) if 'privacy_policy' in self.changed_data: amo.log(amo.LOG.CHANGE_POLICY, self.addon, self.instance) return ob
class AddonFormBasic(AddonFormBase): name = TransField(max_length=50) slug = forms.CharField(max_length=30) summary = TransField(widget=TransTextarea(attrs={'rows': 4}), max_length=250) tags = forms.CharField(required=False) class Meta: model = Addon fields = ('name', 'slug', 'summary', 'tags') def __init__(self, *args, **kw): super(AddonFormBasic, self).__init__(*args, **kw) self.fields['tags'].initial = ', '.join(self.get_tags(self.instance)) # Do not simply append validators, as validators will persist between # instances. def validate_name(name): return clean_name(name, self.instance) name_validators = list(self.fields['name'].validators) name_validators.append(validate_name) self.fields['name'].validators = name_validators def save(self, addon, commit=False): tags_new = self.cleaned_data['tags'] tags_old = [slugify(t, spaces=True) for t in self.get_tags(addon)] # Add new tags. for t in set(tags_new) - set(tags_old): Tag(tag_text=t).save_tag(addon) # Remove old tags. for t in set(tags_old) - set(tags_new): Tag(tag_text=t).remove_tag(addon) # We ignore `commit`, since we need it to be `False` so we can save # the ManyToMany fields on our own. addonform = super(AddonFormBasic, self).save(commit=False) addonform.save() return addonform
class AppFormDetails(addons.forms.AddonFormBase): description = TransField(required=False, label=_(u'Provide a more detailed description of your app'), help_text=_(u'This description will appear on the details page.'), widget=TransTextarea) default_locale = forms.TypedChoiceField(required=False, choices=Addon.LOCALES) homepage = TransField.adapt(forms.URLField)(required=False, verify_exists=False) privacy_policy = TransField(widget=TransTextarea(), required=True, label=_lazy(u"Please specify your app's Privacy Policy")) class Meta: model = Addon fields = ('description', 'default_locale', 'homepage', 'privacy_policy') def clean(self): # Make sure we have the required translations in the new locale. required = 'name', 'summary', 'description' data = self.cleaned_data if not self.errors and 'default_locale' in self.changed_data: fields = dict((k, getattr(self.instance, k + '_id')) for k in required) locale = self.cleaned_data['default_locale'] ids = filter(None, fields.values()) qs = (Translation.objects.filter(locale=locale, id__in=ids, localized_string__isnull=False) .values_list('id', flat=True)) missing = [k for k, v in fields.items() if v not in qs] # They might be setting description right now. if 'description' in missing and locale in data['description']: missing.remove('description') if missing: raise forms.ValidationError( _('Before changing your default locale you must have a ' 'name, summary, and description in that locale. ' 'You are missing %s.') % ', '.join(map(repr, missing))) return data
class ContribForm(TranslationFormMixin, happyforms.ModelForm): RECIPIENTS = (('dev', _lazy(u'The developers of this add-on')), ('moz', _lazy(u'The Mozilla Foundation')), ('org', _lazy(u'An organization of my choice'))) recipient = forms.ChoiceField( choices=RECIPIENTS, widget=forms.RadioSelect(attrs={'class': 'recipient'})) thankyou_note = TransField(widget=TransTextarea(), required=False) class Meta: model = Addon fields = ('paypal_id', 'suggested_amount', 'annoying', 'enable_thankyou', 'thankyou_note') widgets = { 'annoying': forms.RadioSelect(), 'suggested_amount': forms.TextInput(attrs={'class': 'short'}), 'paypal_id': forms.TextInput(attrs={'size': '50'}) } @staticmethod def initial(addon): if addon.charity: recip = 'moz' if addon.charity_id == amo.FOUNDATION_ORG else 'org' else: recip = 'dev' return { 'recipient': recip, 'annoying': addon.annoying or amo.CONTRIB_PASSIVE } def clean(self): if self.instance.upsell: raise forms.ValidationError( _('You cannot setup Contributions for ' 'an add-on that is linked to a premium ' 'add-on in the Marketplace.')) data = self.cleaned_data try: if not self.errors and data['recipient'] == 'dev': check_paypal_id(data['paypal_id']) except forms.ValidationError, e: self.errors['paypal_id'] = self.error_class(e.messages) # thankyou_note is a dict since it's a Translation. if not (data.get('enable_thankyou') and any(data.get('thankyou_note').values())): data['thankyou_note'] = {} data['enable_thankyou'] = False return data
class AppFormTechnical(addons.forms.AddonFormBase): developer_comments = TransField(widget=TransTextarea, required=False) flash = forms.BooleanField(required=False) class Meta: model = Addon fields = ('developer_comments', 'public_stats') def __init__(self, *args, **kw): super(AppFormTechnical, self).__init__(*args, **kw) self.initial['flash'] = self.instance.uses_flash def save(self, addon, commit=False): 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)) return super(AppFormTechnical, self).save(commit=True)
class PreviewForm(happyforms.ModelForm): caption = TransField(widget=TransTextarea, required=False) file_upload = forms.FileField(required=False) upload_hash = forms.CharField(required=False) # This lets us POST the data URIs of the unsaved previews so we can still # show them if there were form errors. unsaved_image_data = forms.CharField(required=False, widget=forms.HiddenInput) unsaved_image_type = forms.CharField(required=False, widget=forms.HiddenInput) def save(self, addon, commit=True): if self.cleaned_data: self.instance.addon = addon if self.cleaned_data.get('DELETE'): # Existing preview. if self.instance.id: self.instance.delete() # User has no desire to save this preview. return super(PreviewForm, self).save(commit=commit) if self.cleaned_data['upload_hash']: upload_hash = self.cleaned_data['upload_hash'] upload_path = os.path.join(settings.TMP_PATH, 'preview', upload_hash) filetype = (os.path.splitext(upload_hash)[1][1:].replace( '-', '/')) if filetype in amo.VIDEO_TYPES: self.instance.update(filetype=filetype) vtasks.resize_video.delay(upload_path, self.instance, user=amo.get_user(), set_modified_on=[self.instance]) else: self.instance.update(filetype='image/png') tasks.resize_preview.delay(upload_path, self.instance, set_modified_on=[self.instance]) class Meta: model = Preview fields = ('caption', 'file_upload', 'upload_hash', 'id', 'position')
class AdminSettingsForm(PreviewForm): DELETE = forms.BooleanField(required=False) mozilla_contact = SeparatedValuesField(forms.EmailField, separator=',', required=False) vip_app = forms.BooleanField(required=False) tags = forms.CharField(required=False) banner_regions = JSONMultipleChoiceField( required=False, choices=mkt.regions.REGIONS_CHOICES_NAME) banner_message = TransField(required=False) class Meta: model = Preview fields = ('file_upload', 'upload_hash', 'position') def __init__(self, *args, **kw): # Get the object for the app's promo `Preview` and pass it to the form. if kw.get('instance'): addon = kw.pop('instance') self.instance = addon self.promo = addon.get_promo() self.request = kw.pop('request', None) # Note: After calling `super`, `self.instance` becomes the `Preview` # object. super(AdminSettingsForm, self).__init__(*args, **kw) self.initial['vip_app'] = addon.vip_app if self.instance: self.initial['mozilla_contact'] = addon.mozilla_contact self.initial['tags'] = ', '.join(self.get_tags(addon)) self.initial['banner_regions'] = addon.geodata.banner_regions or [] self.initial['banner_message'] = addon.geodata.banner_message_id @property def regions_by_id(self): return mkt.regions.REGIONS_CHOICES_ID_DICT def clean_position(self): return -1 def clean_banner_regions(self): try: regions = map(int, self.cleaned_data.get('banner_regions')) except (TypeError, ValueError): # input data is not a list or data contains non-integers. raise forms.ValidationError(_('Invalid region(s) selected.')) return list(regions) def get_tags(self, addon): if acl.action_allowed(self.request, 'Apps', 'Edit'): return list(addon.tags.values_list('tag_text', flat=True)) else: return list(addon.tags.filter(restricted=False) .values_list('tag_text', flat=True)) def clean_tags(self): return clean_tags(self.request, self.cleaned_data['tags']) def clean_mozilla_contact(self): contact = self.cleaned_data.get('mozilla_contact') if self.cleaned_data.get('mozilla_contact') is None: return u'' return contact def save(self, addon, commit=True): if (self.cleaned_data.get('DELETE') and 'upload_hash' not in self.changed_data and self.promo.id): self.promo.delete() elif self.promo and 'upload_hash' in self.changed_data: self.promo.delete() elif self.cleaned_data.get('upload_hash'): super(AdminSettingsForm, self).save(addon, True) contact = self.cleaned_data.get('mozilla_contact') if contact is not None: addon.update(mozilla_contact=contact) vip = self.cleaned_data.get('vip_app') addon.update(vip_app=bool(vip)) tags = self.cleaned_data.get('tags') if tags: tags_new = self.cleaned_data['tags'] tags_old = [slugify(t, spaces=True) for t in self.get_tags(addon)] add_tags = set(tags_new) - set(tags_old) del_tags = set(tags_old) - set(tags_new) # Add new tags. for t in add_tags: Tag(tag_text=t).save_tag(addon) # Remove old tags. for t in del_tags: Tag(tag_text=t).remove_tag(addon) geodata = addon.geodata geodata.banner_regions = self.cleaned_data.get('banner_regions') geodata.banner_message = self.cleaned_data.get('banner_message') geodata.save() uses_flash = self.cleaned_data.get('flash') af = addon.get_latest_file() if af is not None: af.update(uses_flash=bool(uses_flash)) index_webapps.delay([addon.id]) return addon
class AddonFormBasic(AddonFormBase): name = TransField(max_length=50) slug = forms.CharField(max_length=30) summary = TransField(widget=TransTextarea(attrs={'rows': 4}), max_length=250) tags = forms.CharField(required=False) class Meta: model = Addon fields = ('name', 'slug', 'summary', 'tags') def __init__(self, *args, **kw): # Force the form to use app_slug if this is a webapp. We want to keep # this under "slug" so all the js continues to work. if kw['instance'].is_webapp(): kw.setdefault('initial', {})['slug'] = kw['instance'].app_slug super(AddonFormBasic, self).__init__(*args, **kw) self.fields['tags'].initial = ', '.join(self.get_tags(self.instance)) # Do not simply append validators, as validators will persist between # instances. validate_name = lambda x: clean_name(x, self.instance) name_validators = list(self.fields['name'].validators) name_validators.append(validate_name) self.fields['name'].validators = name_validators def save(self, addon, commit=False): tags_new = self.cleaned_data['tags'] tags_old = [slugify(t, spaces=True) for t in self.get_tags(addon)] # Add new tags. for t in set(tags_new) - set(tags_old): Tag(tag_text=t).save_tag(addon) # Remove old tags. for t in set(tags_old) - set(tags_new): Tag(tag_text=t).remove_tag(addon) # We ignore `commit`, since we need it to be `False` so we can save # the ManyToMany fields on our own. addonform = super(AddonFormBasic, self).save(commit=False) addonform.save() return addonform def _post_clean(self): if self.instance.is_webapp(): # Switch slug to app_slug in cleaned_data and self._meta.fields so # we can update the app_slug field for webapps. try: self._meta.fields = list(self._meta.fields) slug_idx = self._meta.fields.index('slug') data = self.cleaned_data if 'slug' in data: data['app_slug'] = data.pop('slug') self._meta.fields[slug_idx] = 'app_slug' super(AddonFormBasic, self)._post_clean() finally: self._meta.fields[slug_idx] = 'slug' else: super(AddonFormBasic, self)._post_clean()
class AppFormBasic(addons.forms.AddonFormBase): """Form to edit basic app info.""" name = TransField(max_length=128, widget=TransInput) slug = forms.CharField(max_length=30, widget=forms.TextInput) manifest_url = forms.URLField(verify_exists=False) summary = TransField(widget=TransTextarea(attrs={'rows': 4}), max_length=250) class Meta: model = Addon fields = ('name', 'slug', 'manifest_url', 'summary') def __init__(self, *args, **kw): # Force the form to use app_slug if this is a webapp. We want to keep # this under "slug" so all the js continues to work. if kw['instance'].is_webapp(): kw.setdefault('initial', {})['slug'] = kw['instance'].app_slug super(AppFormBasic, self).__init__(*args, **kw) # Do not simply append validators, as validators will persist between # instances. validate_name = lambda x: clean_name(x, self.instance) name_validators = list(self.fields['name'].validators) name_validators.append(validate_name) self.fields['name'].validators = name_validators def _post_clean(self): # Switch slug to app_slug in cleaned_data and self._meta.fields so # we can update the app_slug field for webapps. try: self._meta.fields = list(self._meta.fields) slug_idx = self._meta.fields.index('slug') data = self.cleaned_data if 'slug' in data: data['app_slug'] = data.pop('slug') self._meta.fields[slug_idx] = 'app_slug' super(AppFormBasic, self)._post_clean() finally: self._meta.fields[slug_idx] = 'slug' def clean_slug(self): target = self.cleaned_data['slug'] slug_validator(target, lower=False) slug_field = 'app_slug' if self.instance.is_webapp() else 'slug' 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 clean_manifest_url(self): manifest_url = self.cleaned_data['manifest_url'] # Only verify if manifest changed. if 'manifest_url' in self.changed_data: # Only Admins can edit the manifest_url. if not acl.action_allowed(self.request, 'Admin', '%'): return self.instance.manifest_url verify_app_domain(manifest_url, exclude=self.instance) return manifest_url def save(self, addon, commit=False): # We ignore `commit`, since we need it to be `False` so we can save # the ManyToMany fields on our own. addonform = super(AppFormBasic, self).save(commit=False) addonform.save() return addonform
class AppFormBasic(AddonFormBasic): """Form to override name length for apps.""" name = TransField(max_length=128)
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 = self.cleaned_data['app_slug'] slug_validator(slug, lower=False) if slug != self.instance.app_slug: if Webapp.objects.filter(app_slug=slug).exists(): raise forms.ValidationError( _('This slug is already in use. Please choose another.')) if BlacklistedSlug.blocked(slug): raise forms.ValidationError( _('The slug cannot be "%s". Please choose another.' % slug)) return slug 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
class EditThemeForm(AddonFormBase): name = TransField(max_length=50, label=_lazy('Give Your Theme a Name.')) slug = forms.CharField(max_length=30) category = forms.ModelChoiceField(queryset=Category.objects.all(), widget=forms.widgets.RadioSelect) description = TransField(widget=TransTextarea(attrs={'rows': 4}), max_length=500, required=False, label=_lazy('Describe your Theme.')) tags = forms.CharField(required=False) accentcolor = ColorField(required=False) textcolor = ColorField(required=False) license = forms.TypedChoiceField( choices=amo.PERSONA_LICENSES_CHOICES, coerce=int, empty_value=None, widget=forms.HiddenInput, error_messages={'required': _lazy(u'A license must be selected.')}) # Theme re-upload. header = forms.FileField(required=False) header_hash = forms.CharField(widget=forms.HiddenInput, required=False) footer = forms.FileField(required=False) footer_hash = forms.CharField(widget=forms.HiddenInput, required=False) class Meta: model = Addon fields = ('name', 'slug', 'description', 'tags') def __init__(self, *args, **kw): self.request = kw.pop('request') super(AddonFormBase, self).__init__(*args, **kw) addon = Addon.objects.no_cache().get(id=self.instance.id) persona = addon.persona # Do not simply append validators, as validators will persist between # instances. self.fields['name'].validators = list(self.fields['name'].validators) self.fields['name'].validators.append(lambda x: clean_name(x, addon)) # Allow theme artists to localize Name and Description. for trans in Translation.objects.filter(id=self.initial['name']): self.initial['name_' + trans.locale.lower()] = trans for trans in Translation.objects.filter( id=self.initial['description']): self.initial['description_' + trans.locale.lower()] = trans self.old_tags = self.get_tags(addon) self.initial['tags'] = ', '.join(self.old_tags) if persona.accentcolor: self.initial['accentcolor'] = '#' + persona.accentcolor if persona.textcolor: self.initial['textcolor'] = '#' + persona.textcolor self.initial['license'] = persona.license cats = sorted(Category.objects.filter(type=amo.ADDON_PERSONA, weight__gte=0), key=lambda x: x.name) self.fields['category'].choices = [(c.id, c.name) for c in cats] try: self.initial['category'] = addon.categories.values_list( 'id', flat=True)[0] except IndexError: pass for field in ('header', 'footer'): self.fields[field].widget.attrs = { 'data-upload-url': reverse('devhub.personas.reupload_persona', args=[addon.slug, 'persona_%s' % field]), 'data-allowed-types': 'image/jpeg|image/png' } def save(self): addon = self.instance persona = addon.persona data = self.cleaned_data # Update Persona-specific data. persona_data = { 'license': int(data['license']), 'accentcolor': data['accentcolor'].lstrip('#'), 'textcolor': data['textcolor'].lstrip('#'), 'author': self.request.amo_user.username, 'display_username': self.request.amo_user.name } changed = False for k, v in persona_data.iteritems(): if v != getattr(persona, k): changed = True setattr(persona, k, v) if changed: persona.save() if self.changed_data: amo.log(amo.LOG.EDIT_PROPERTIES, addon) self.instance.modified = datetime.now() # Update Addon-specific data. changed = ( set(self.old_tags) != data['tags'] or # Check if tags changed. self.initial['slug'] != data['slug'] or # Check if slug changed. transfield_changed('description', self.initial, data) or transfield_changed('name', self.initial, data)) if changed: # Only save if addon data changed. super(EditThemeForm, self).save() # Update tags. tags_new = data['tags'] tags_old = [slugify(t, spaces=True) for t in self.old_tags] # Add new tags. for t in set(tags_new) - set(tags_old): Tag(tag_text=t).save_tag(addon) # Remove old tags. for t in set(tags_old) - set(tags_new): Tag(tag_text=t).remove_tag(addon) # Update category. if data['category'].id != self.initial['category']: addon_cat = addon.addoncategory_set.all()[0] addon_cat.category = data['category'] addon_cat.save() # Theme reupload. if not addon.is_pending(): if data['header_hash'] or data['footer_hash']: save_theme_reupload.delay(data['header_hash'], data['footer_hash'], addon) return data
class AppFormBasic(addons.forms.AddonFormBase): """Form to edit basic app info.""" slug = forms.CharField(max_length=30, widget=forms.TextInput) manifest_url = forms.URLField() description = TransField( required=True, label=_lazy(u'Provide a detailed description of your app'), help_text=_lazy(u'This description will appear on the details page.'), widget=TransTextarea) class Meta: model = Addon fields = ('slug', 'manifest_url', 'description') def __init__(self, *args, **kw): # Force the form to use app_slug if this is a webapp. We want to keep # this under "slug" so all the js continues to work. if kw['instance'].is_webapp(): kw.setdefault('initial', {})['slug'] = kw['instance'].app_slug super(AppFormBasic, self).__init__(*args, **kw) if self.instance.is_packaged: # Manifest URL field for packaged apps is empty. self.fields['manifest_url'].required = False def _post_clean(self): # Switch slug to app_slug in cleaned_data and self._meta.fields so # we can update the app_slug field for webapps. try: self._meta.fields = list(self._meta.fields) slug_idx = self._meta.fields.index('slug') data = self.cleaned_data if 'slug' in data: data['app_slug'] = data.pop('slug') self._meta.fields[slug_idx] = 'app_slug' super(AppFormBasic, self)._post_clean() finally: self._meta.fields[slug_idx] = 'slug' def clean_slug(self): slug = self.cleaned_data['slug'] slug_validator(slug, lower=False) if slug != self.instance.app_slug: if Webapp.objects.filter(app_slug=slug).exists(): raise forms.ValidationError( _('This slug is already in use. Please choose another.')) if BlacklistedSlug.blocked(slug): raise forms.ValidationError( _('The slug cannot be "%s". ' 'Please choose another.' % slug)) return slug.lower() def clean_manifest_url(self): manifest_url = self.cleaned_data['manifest_url'] # Only verify if manifest changed. if 'manifest_url' in self.changed_data: # Only Admins can edit the manifest_url. if not acl.action_allowed(self.request, 'Admin', '%'): return self.instance.manifest_url verify_app_domain(manifest_url, exclude=self.instance) return manifest_url def save(self, addon, commit=False): # We ignore `commit`, since we need it to be `False` so we can save # the ManyToMany fields on our own. addonform = super(AppFormBasic, self).save(commit=False) addonform.save() return addonform
class AdminSettingsForm(PreviewForm): DELETE = forms.BooleanField(required=False) mozilla_contact = SeparatedValuesField(forms.EmailField, separator=',', required=False) tags = forms.CharField(required=False) app_ratings = forms.MultipleChoiceField(required=False) banner_regions = JSONMultipleChoiceField( required=False, choices=mkt.regions.REGIONS_CHOICES_NAME) banner_message = TransField(required=False) class Meta: model = Preview fields = ('file_upload', 'upload_hash', 'position') def __init__(self, *args, **kw): # Get the object for the app's promo `Preview` and pass it to the form. if kw.get('instance'): addon = kw.pop('instance') self.instance = addon self.promo = addon.get_promo() self.request = kw.pop('request', None) self.base_fields['app_ratings'].choices = RATINGS_BY_NAME() self.disabled_regions = sorted(addon.get_excluded_region_ids()) # Note: After calling `super`, `self.instance` becomes the `Preview` # object. super(AdminSettingsForm, self).__init__(*args, **kw) if self.instance: self.initial['mozilla_contact'] = addon.mozilla_contact self.initial['tags'] = ', '.join(self.get_tags(addon)) app_ratings = [] for acr in addon.content_ratings.all(): rating = RATINGS_BODIES[acr.ratings_body].ratings[acr.rating] try: app_ratings.append(ALL_RATINGS().index(rating)) except ValueError: pass # Due to waffled ratings bodies. self.initial['app_ratings'] = app_ratings self.initial['banner_regions'] = addon.geodata.banner_regions or [] self.initial['banner_message'] = addon.geodata.banner_message_id @property def regions_by_id(self): return mkt.regions.REGIONS_CHOICES_ID_DICT def clean_position(self): return -1 def clean_app_ratings(self): ratings_ids = self.cleaned_data.get('app_ratings') ratings = [ALL_RATINGS()[int(i)] for i in ratings_ids] ratingsbodies = set([r.ratingsbody for r in ratings]) if len(ratingsbodies) != len(ratings): raise forms.ValidationError( _('Only one rating from each ratings ' 'body may be selected.')) return ratings_ids def clean_banner_regions(self): try: regions = map(int, self.cleaned_data.get('banner_regions')) except (TypeError, ValueError): # input data is not a list or data contains non-integers. raise forms.ValidationError(_('Invalid region(s) selected.')) if set(regions).intersection(self.disabled_regions): raise forms.ValidationError( _('Only regions the app is already ' 'available in may be selected.')) return list(regions) def get_tags(self, addon): if acl.action_allowed(self.request, 'Apps', 'Edit'): return list(addon.tags.values_list('tag_text', flat=True)) else: return list( addon.tags.filter(restricted=False).values_list('tag_text', flat=True)) def clean_tags(self): return clean_tags(self.request, self.cleaned_data['tags']) def save(self, addon, commit=True): if (self.cleaned_data.get('DELETE') and 'upload_hash' not in self.changed_data and self.promo.id): self.promo.delete() elif self.promo and 'upload_hash' in self.changed_data: self.promo.delete() elif self.cleaned_data.get('upload_hash'): super(AdminSettingsForm, self).save(addon, True) contact = self.cleaned_data.get('mozilla_contact') if contact: addon.update(mozilla_contact=contact) tags = self.cleaned_data.get('tags') if tags: tags_new = self.cleaned_data['tags'] tags_old = [slugify(t, spaces=True) for t in self.get_tags(addon)] add_tags = set(tags_new) - set(tags_old) del_tags = set(tags_old) - set(tags_new) # Add new tags. for t in add_tags: Tag(tag_text=t).save_tag(addon) # Remove old tags. for t in del_tags: Tag(tag_text=t).remove_tag(addon) # Content ratings. ratings = self.cleaned_data.get('app_ratings') if ratings: ratings = [ALL_RATINGS()[int(i)] for i in ratings] # Delete content ratings with ratings body not in new set. r_bodies = set([rating.ratingsbody.id for rating in ratings]) addon.content_ratings.exclude(ratings_body__in=r_bodies).delete() # Set content ratings, takes {<ratingsbody class>: <rating class>}. addon.set_content_ratings( dict((rating.ratingsbody, rating) for rating in ratings)) else: addon.content_ratings.all().delete() geodata = addon.geodata geodata.banner_regions = self.cleaned_data.get('banner_regions') geodata.banner_message = self.cleaned_data.get('banner_message') geodata.save() toggle_game(addon) uses_flash = self.cleaned_data.get('flash') af = addon.get_latest_file() if af is not None: af.update(uses_flash=bool(uses_flash)) index_webapps.delay([addon.id]) return addon
class AddonFormBasic(AddonFormBase): name = TransField(max_length=50) slug = forms.CharField(max_length=30) summary = TransField(widget=TransTextarea(attrs={'rows': 4}), max_length=250) tags = forms.CharField(required=False) class Meta: model = Addon fields = ('name', 'slug', 'summary', 'tags') def __init__(self, *args, **kw): # Force the form to use app_slug if this is a webapp. We want to keep # this under "slug" so all the js continues to work. if kw['instance'].is_webapp(): kw.setdefault('initial', {})['slug'] = kw['instance'].app_slug super(AddonFormBasic, self).__init__(*args, **kw) self.fields['tags'].initial = ', '.join(self.get_tags(self.instance)) # Do not simply append validators, as validators will persist between # instances. validate_name = lambda x: clean_name(x, self.instance) name_validators = list(self.fields['name'].validators) name_validators.append(validate_name) self.fields['name'].validators = name_validators def get_tags(self, addon): if acl.action_allowed(self.request, 'Addons', 'Edit'): return [t.tag_text for t in addon.tags.all()] else: return [t.tag_text for t in addon.tags.filter(restricted=False)] def save(self, addon, commit=False): tags_new = self.cleaned_data['tags'] tags_old = [slugify(t, spaces=True) for t in self.get_tags(addon)] # Add new tags. for t in set(tags_new) - set(tags_old): Tag(tag_text=t).save_tag(addon) # Remove old tags. for t in set(tags_old) - set(tags_new): Tag(tag_text=t).remove_tag(addon) # We ignore `commit`, since we need it to be `False` so we can save # the ManyToMany fields on our own. addonform = super(AddonFormBasic, self).save(commit=False) addonform.save() return addonform def _post_clean(self): if self.instance.is_webapp(): # Switch slug to app_slug in cleaned_data and self._meta.fields so # we can update the app_slug field for webapps. try: self._meta.fields = list(self._meta.fields) slug_idx = self._meta.fields.index('slug') data = self.cleaned_data if 'slug' in data: data['app_slug'] = data.pop('slug') self._meta.fields[slug_idx] = 'app_slug' super(AddonFormBasic, self)._post_clean() finally: self._meta.fields[slug_idx] = 'slug' else: super(AddonFormBasic, self)._post_clean() def clean_tags(self): return clean_tags(self.request, self.cleaned_data['tags']) def clean_slug(self): target = self.cleaned_data['slug'] slug_validator(target, lower=False) slug_field = 'app_slug' if self.instance.is_webapp() else 'slug' 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
class AddonFormBasic(AddonFormBase): name = TransField(max_length=50) slug = forms.CharField(max_length=30) summary = TransField(widget=TransTextarea(attrs={'rows': 4}), max_length=250) tags = forms.CharField(required=False) class Meta: model = Addon fields = ('name', 'slug', 'summary', 'tags') def __init__(self, *args, **kw): super(AddonFormBasic, self).__init__(*args, **kw) self.fields['tags'].initial = ', '.join(self.get_tags(self.instance)) # Do not simply append validators, as validators will persist between # instances. validate_name = lambda x: clean_name(x, self.instance) name_validators = list(self.fields['name'].validators) name_validators.append(validate_name) self.fields['name'].validators = name_validators def get_tags(self, addon): if acl.action_allowed(self.request, 'Admin', 'EditAnyAddon'): return [t.tag_text for t in addon.tags.all()] else: return [t.tag_text for t in addon.tags.filter(restricted=False)] def save(self, addon, commit=False): tags_new = self.cleaned_data['tags'] tags_old = [slugify(t, spaces=True) for t in self.get_tags(addon)] # Add new tags. for t in set(tags_new) - set(tags_old): Tag(tag_text=t).save_tag(addon) # Remove old tags. for t in set(tags_old) - set(tags_new): Tag(tag_text=t).remove_tag(addon) # We ignore `commit`, since we need it to be `False` so we can save # the ManyToMany fields on our own. addonform = super(AddonFormBasic, self).save(commit=False) addonform.save() return addonform def clean_tags(self): return clean_tags(self.request, self.cleaned_data['tags']) def clean_slug(self): target = self.cleaned_data['slug'] slug_validator(target, lower=False) if target != self.instance.slug: if Addon.objects.filter(slug=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
class UserEditForm(UserRegisterForm): photo = forms.FileField( label=_lazy(u'Profile Photo'), required=False, help_text=_lazy(u'PNG and JPG supported. Large images will be resized ' 'to fit 200 x 200 px.')) display_name = forms.CharField( label=_lazy(u'Display Name'), max_length=50, required=False, help_text=_lazy(u'This will be publicly displayed next to your ' 'ratings, collections, and other contributions.')) notifications = forms.MultipleChoiceField( required=False, choices=[], widget=NotificationsSelectMultiple, initial=email.APP_NOTIFICATIONS_DEFAULT) password = forms.CharField(required=False) password2 = forms.CharField(required=False) bio = TransField(label=_lazy(u'Bio'), required=False, widget=TransTextarea(attrs={'rows': 4})) def __init__(self, *args, **kwargs): self.request = kwargs.pop('request', None) super(UserEditForm, self).__init__(*args, **kwargs) if self.instance: default = dict((i, n.default_checked) for i, n in email.APP_NOTIFICATIONS_BY_ID.items()) user = dict((n.notification_id, n.enabled) for n in self.instance.notifications.all()) default.update(user) # Add choices to Notification. choices = email.APP_NOTIFICATIONS_CHOICES if not self.instance.read_dev_agreement: choices = email.APP_NOTIFICATIONS_CHOICES_NOT_DEV self.fields['notifications'].choices = choices self.fields['notifications'].initial = [ i for i, v in default.items() if v ] self.fields['notifications'].widget.form_instance = self # TODO: We should inherit from a base form not UserRegisterForm. if self.fields.get('recaptcha'): del self.fields['recaptcha'] class Meta: model = UserProfile fields = ('username', 'display_name', 'location', 'occupation', 'bio', 'homepage') def clean_photo(self): photo = self.cleaned_data.get('photo') if photo: if photo.content_type not in ('image/png', 'image/jpeg'): raise forms.ValidationError( _('Images must be either PNG or JPG.')) if photo.size > settings.MAX_PHOTO_UPLOAD_SIZE: raise forms.ValidationError( _('Please use images smaller than %dMB.' % (settings.MAX_PHOTO_UPLOAD_SIZE / 1024 / 1024 - 1))) return photo def save(self): u = super(UserEditForm, self).save(commit=False) data = self.cleaned_data photo = data['photo'] if photo: u.picture_type = 'image/png' tmp_destination = u.picture_path + '__unconverted' if not os.path.exists(u.picture_dir): os.makedirs(u.picture_dir) fh = open(tmp_destination, 'w') for chunk in photo.chunks(): fh.write(chunk) fh.close() resize_photo.delay(tmp_destination, u.picture_path, set_modified_on=[u]) for i, n in email.APP_NOTIFICATIONS_BY_ID.iteritems(): enabled = n.mandatory or (str(i) in data['notifications']) UserNotification.update_or_create(user=u, notification_id=i, update={'enabled': enabled}) log.debug(u'User (%s) updated their profile' % u) u.save() return u
class AddonFormBasic(AddonFormBase): name = TransField(max_length=50) slug = forms.CharField(max_length=30) summary = TransField(widget=TransTextarea(attrs={'rows': 4}), max_length=250) tags = forms.CharField(required=False) class Meta: model = Addon fields = ('name', 'slug', 'summary', 'tags') def __init__(self, *args, **kw): super(AddonFormBasic, self).__init__(*args, **kw) self.fields['tags'].initial = ', '.join(self.get_tags(self.instance)) # Do not simply append validators, as validators will persist between # instances. validate_name = lambda x: clean_name(x, self.instance) name_validators = list(self.fields['name'].validators) name_validators.append(validate_name) self.fields['name'].validators = name_validators def get_tags(self, addon): if acl.action_allowed(self.request, 'Admin', 'EditAnyAddon'): return [t.tag_text for t in addon.tags.all()] else: return [t.tag_text for t in addon.tags.filter(restricted=False)] def save(self, addon, commit=False): tags_new = self.cleaned_data['tags'] tags_old = [slugify(t, spaces=True) for t in self.get_tags(addon)] # Add new tags. for t in set(tags_new) - set(tags_old): Tag(tag_text=t).save_tag(addon) # Remove old tags. for t in set(tags_old) - set(tags_new): Tag(tag_text=t).remove_tag(addon) # We ignore `commit`, since we need it to be `False` so we can save # the ManyToMany fields on our own. addonform = super(AddonFormBasic, self).save(commit=False) addonform.save() return addonform def clean_tags(self): target = [slugify(t, spaces=True, lower=True) for t in self.cleaned_data['tags'].split(',')] target = set(filter(None, target)) min_len = amo.MIN_TAG_LENGTH max_len = Tag._meta.get_field('tag_text').max_length max_tags = amo.MAX_TAGS total = len(target) blacklisted = (Tag.objects.values_list('tag_text', flat=True) .filter(tag_text__in=target, blacklisted=True)) if blacklisted: # L10n: {0} is a single tag or a comma-separated list of tags. msg = ngettext('Invalid tag: {0}', 'Invalid tags: {0}', len(blacklisted)).format(', '.join(blacklisted)) raise forms.ValidationError(msg) restricted = (Tag.objects.values_list('tag_text', flat=True) .filter(tag_text__in=target, restricted=True)) if not acl.action_allowed(self.request, 'Admin', 'EditAnyAddon'): if restricted: # L10n: {0} is a single tag or a comma-separated list of tags. msg = ngettext('"{0}" is a reserved tag and cannot be used.', '"{0}" are reserved tags and cannot be used.', len(restricted)).format('", "'.join(restricted)) raise forms.ValidationError(msg) else: # Admin's restricted tags don't count towards the limit. total = len(target - set(restricted)) if total > max_tags: num = total - max_tags msg = ngettext('You have {0} too many tags.', 'You have {0} too many tags.', num).format(num) raise forms.ValidationError(msg) if any(t for t in target if len(t) > max_len): raise forms.ValidationError(_('All tags must be %s characters ' 'or less after invalid characters are removed.' % max_len)) if any(t for t in target if len(t) < min_len): msg = ngettext("All tags must be at least {0} character.", "All tags must be at least {0} characters.", min_len).format(min_len) raise forms.ValidationError(msg) return target def clean_slug(self): target = self.cleaned_data['slug'] slug_validator(target, lower=False) if target != self.instance.slug: if Addon.objects.filter(slug=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