class AppFormSupport(AppSupportFormMixin, AddonFormBase): support_url = TransField.adapt(forms.URLField)(required=False) support_email = TransField.adapt(forms.EmailField)(required=False) class Meta: model = Webapp fields = ('support_email', 'support_url')
class AppFormDetails(AddonFormBase): LOCALES = [(translation.to_locale(k).replace('_', '-'), v) for k, v in do_dictsort(settings.LANGUAGES)] default_locale = forms.TypedChoiceField(required=False, choices=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 = Webapp 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 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, label=_lazy(u'Make this the Active version of my app as soon as it ' u'has been reviewed and approved.')) 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.publish_type == mkt.PUBLISH_IMMEDIATE) def save(self, *args, **kwargs): rval = super(AppVersionForm, self).save(*args, **kwargs) if self.instance.all_files[0].status == mkt.STATUS_PENDING: # If version is pending, allow changes to publish_type. if self.cleaned_data.get('publish_immediately'): publish_type = mkt.PUBLISH_IMMEDIATE else: publish_type = mkt.PUBLISH_PRIVATE self.instance.addon.update(publish_type=publish_type) return rval
class AppDetailsBasicForm(TranslationFormMixin, happyforms.ModelForm): """Form for "Details" submission step.""" PRIVACY_MDN_URL = ('https://developer.mozilla.org/Marketplace/' 'Publishing/Policies_and_Guidelines/Privacy_policies') PUBLISH_CHOICES = ( (amo.PUBLISH_IMMEDIATE, _lazy(u'Publish my app and make it visible to everyone in the ' u'Marketplace and include it in search results.')), (amo.PUBLISH_PRIVATE, _lazy(u'Do not publish my app. Notify me and I will adjust app ' u'visibility after it is approved.')), ) app_slug = forms.CharField(max_length=30, widget=forms.TextInput(attrs={'class': 'm'})) description = TransField( label=_lazy(u'Description:'), help_text=_lazy(u'This description will appear on the details page.'), widget=TransTextarea(attrs={'rows': 4})) privacy_policy = TransField( label=_lazy(u'Privacy Policy:'), widget=TransTextarea(attrs={'rows': 6}), help_text=_lazy( u'A privacy policy explains how you handle data received ' u'through your app. For example: what data do you receive? ' u'How do you use it? Who do you share it with? Do you ' u'receive personal information? Do you take steps to make ' u'it anonymous? What choices do users have to control what ' u'data you and others receive? Enter your privacy policy ' u'link or text above. If you don\'t have a privacy ' u'policy, <a href="{url}" target="_blank">learn more on how to ' u'write one.</a>')) homepage = TransField.adapt(forms.URLField)( label=_lazy(u'Homepage:'), required=False, widget=TransInput(attrs={'class': 'full'}), help_text=_lazy( u'If your app has another homepage, enter its address here.')) support_url = TransField.adapt(forms.URLField)( label=_lazy(u'Support Website:'), required=False, widget=TransInput(attrs={'class': 'full'}), help_text=_lazy( u'If your app has a support website or forum, enter its address ' u'here.')) support_email = TransField.adapt(forms.EmailField)( label=_lazy(u'Support Email:'), widget=TransInput(attrs={'class': 'full'}), help_text=_lazy( u'This email address will be listed publicly on the Marketplace ' u'and used by end users to contact you with support issues. This ' u'email address will be listed publicly on your app details page.') ) flash = forms.TypedChoiceField( label=_lazy(u'Does your app require Flash support?'), required=False, coerce=lambda x: bool(int(x)), initial=0, widget=forms.RadioSelect, choices=((1, _lazy(u'Yes')), (0, _lazy(u'No')))) notes = forms.CharField( label=_lazy(u'Your comments for reviewers'), required=False, widget=forms.Textarea(attrs={'rows': 2}), help_text=_lazy( u'Your app will be reviewed by Mozilla before it becomes publicly ' u'listed on the Marketplace. Enter any special instructions for ' u'the app reviewers here.')) publish_type = forms.TypedChoiceField( label=_lazy(u'Once your app is approved, choose a publishing option:'), choices=PUBLISH_CHOICES, initial=amo.PUBLISH_IMMEDIATE, widget=forms.RadioSelect()) class Meta: model = Webapp fields = ('app_slug', 'description', 'privacy_policy', 'homepage', 'support_url', 'support_email', 'publish_type') def __init__(self, *args, **kwargs): self.request = kwargs.pop('request') # TODO: remove this and put it in the field definition above. # See https://bugzilla.mozilla.org/show_bug.cgi?id=1072513 privacy_field = self.base_fields['privacy_policy'] privacy_field.help_text = mark_safe( privacy_field.help_text.format(url=self.PRIVACY_MDN_URL)) super(AppDetailsBasicForm, self).__init__(*args, **kwargs) 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.lower() def save(self, *args, **kw): if self.data['notes']: create_comm_note(self.instance, self.instance.versions.latest(), self.request.user, self.data['notes'], note_type=comm.SUBMISSION) self.instance = super(AppDetailsBasicForm, self).save(commit=True) 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 self.instance
class WebsiteForm(TranslationFormMixin, happyforms.ModelForm): categories = forms.MultipleChoiceField(label=_lazy(u'Categories'), choices=CATEGORY_CHOICES, widget=forms.CheckboxSelectMultiple) description = TransField(label=_lazy(u'Description'), widget=TransTextarea(attrs={'rows': 4})) devices = forms.MultipleChoiceField(label=_lazy(u'Compatible Devices'), choices=DEVICE_CHOICES, widget=forms.SelectMultiple) keywords = forms.CharField(label=_lazy(u'Keywords'), required=False, widget=forms.Textarea(attrs={'rows': 2})) name = TransField(label=_lazy(u'Name'), widget=TransInput()) preferred_regions = forms.MultipleChoiceField( label=_lazy(u'Preferred Regions'), choices=REGIONS_CHOICES_NAME, required=False, widget=forms.SelectMultiple(attrs={'size': 10})) short_name = TransField(label=_lazy(u'Short Name'), widget=TransInput(), required=False) title = TransField(label=_lazy(u'Title'), widget=TransInput(), required=False) url = forms.URLField(label=_lazy(u'URL')) class Meta(object): model = Website fields = ('categories', 'description', 'devices', 'is_disabled', 'keywords', 'mobile_url', 'name', 'preferred_regions', 'short_name', 'status', 'title', 'tv_url', 'url') def __init__(self, *args, **kwargs): self.request = kwargs.pop('request') super(WebsiteForm, self).__init__(*args, **kwargs) keywords = self.instance.keywords.values_list('tag_text', flat=True) self.initial['keywords'] = ', '.join(keywords) def clean_categories(self): categories = self.cleaned_data['categories'] max_cat = mkt.MAX_CATEGORIES if len(set(categories)) > max_cat: # L10n: {0} is the number of categories. raise forms.ValidationError( ngettext('You can have only {0} category.', 'You can have only {0} categories.', max_cat).format(max_cat)) return categories def clean_keywords(self): # We set a high `max_tags` here b/c the keywords data is coming from # the website meta data which can contain a higher number of tags than # apps. return clean_tags(self.request, self.cleaned_data['keywords'], max_tags=100) def clean_preferred_regions(self): try: regions = map(int, self.cleaned_data.get('preferred_regions')) except (TypeError, ValueError): # Data is not a list or data contains non-integers. raise forms.ValidationError(_('Invalid region(s) selected.')) return list(regions) def clean_devices(self): try: devices = map(int, self.cleaned_data.get('devices')) except (TypeError, ValueError): # Data is not a list or data contains non-integers. raise forms.ValidationError(_('Invalid device(s) selected.')) return list(devices) def save(self, commit=False): form = super(WebsiteForm, self).save(commit=False) keywords_new = self.cleaned_data['keywords'] keywords_old = [ slugify(keyword, spaces=True) for keyword in self.instance.keywords.values_list('tag_text', flat=True) ] for k in set(keywords_new) - set(keywords_old): Tag(tag_text=k).save_tag(self.instance) for k in set(keywords_old) - set(keywords_new): Tag(tag_text=k).remove_tag(self.instance) form.save() return form
class AppFormBasic(AddonFormBase): """Form to edit basic app info.""" slug = forms.CharField(max_length=30, widget=forms.TextInput) manifest_url = forms.URLField() hosted_url = forms.CharField( label=_lazy(u'Hosted URL:'), required=False, help_text=_lazy( u'A URL to where your app is hosted on the web, if it exists. This' u' allows users to try out your app before installing it.')) 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) tags = forms.CharField( label=_lazy(u'Search Keywords:'), required=False, widget=forms.Textarea(attrs={'rows': 3}), help_text=_lazy( u'The search keywords are used to return search results in the ' u'Firefox Marketplace. Be sure to include a keywords that ' u'accurately reflect your app.')) class Meta: model = Webapp fields = ('slug', 'manifest_url', 'hosted_url', 'description', 'tags') def __init__(self, *args, **kw): # Force the form to use app_slug. We want to keep # this under "slug" so all the js continues to work. kw.setdefault('initial', {})['slug'] = kw['instance'].app_slug super(AppFormBasic, self).__init__(*args, **kw) self.old_manifest_url = self.instance.manifest_url if self.instance.is_packaged: # Manifest URL cannot be changed for packaged apps. del self.fields['manifest_url'] self.initial['tags'] = ', '.join(self.get_tags(self.instance)) def clean_tags(self): return clean_tags(self.request, self.cleaned_data['tags']) def get_tags(self, addon): if can_edit_restricted_tags(self.request): 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 _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 BlockedSlug.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: 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() if 'manifest_url' in self.changed_data: before_url = self.old_manifest_url after_url = self.cleaned_data['manifest_url'] # If a non-admin edited the manifest URL, add to Re-review Queue. if not acl.action_allowed(self.request, 'Admin', '%'): log.info(u'[Webapp:%s] (Re-review) Manifest URL changed ' u'from %s to %s' % (self.instance, before_url, after_url)) msg = (_(u'Manifest URL changed from {before_url} to ' u'{after_url}') .format(before_url=before_url, after_url=after_url)) RereviewQueue.flag(self.instance, mkt.LOG.REREVIEW_MANIFEST_URL_CHANGE, msg) # Refetch the new manifest. log.info('Manifest %s refreshed for %s' % (addon.manifest_url, addon)) update_manifests.delay([self.instance.id]) 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) return addonform
class AppFormBasic(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 = Webapp fields = ('slug', 'manifest_url', 'description') def __init__(self, *args, **kw): # Force the form to use app_slug. We want to keep # this under "slug" so all the js continues to work. kw.setdefault('initial', {})['slug'] = kw['instance'].app_slug super(AppFormBasic, self).__init__(*args, **kw) self.old_manifest_url = self.instance.manifest_url if self.instance.is_packaged: # Manifest URL cannot be changed for packaged apps. del self.fields['manifest_url'] 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: 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() if 'manifest_url' in self.changed_data: before_url = self.old_manifest_url after_url = self.cleaned_data['manifest_url'] # If a non-admin edited the manifest URL, add to Re-review Queue. if not acl.action_allowed(self.request, 'Admin', '%'): log.info(u'[Webapp:%s] (Re-review) Manifest URL changed ' u'from %s to %s' % (self.instance, before_url, after_url)) msg = (_(u'Manifest URL changed from {before_url} to ' u'{after_url}').format(before_url=before_url, after_url=after_url)) RereviewQueue.flag(self.instance, amo.LOG.REREVIEW_MANIFEST_URL_CHANGE, msg) # Refetch the new manifest. log.info('Manifest %s refreshed for %s' % (addon.manifest_url, addon)) update_manifests.delay([self.instance.id]) return addonform
class AdminSettingsForm(PreviewForm): DELETE = forms.BooleanField(required=False) mozilla_contact = SeparatedValuesField(forms.EmailField, separator=',', required=False) vip_app = forms.BooleanField(required=False) priority_review = 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): # Note that this form is not inheriting from AddonFormBase, so we have # to get rid of 'version' ourselves instead of letting the parent class # do it. kw.pop('version', None) # 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 self.initial['priority_review'] = addon.priority_review 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) updates = { 'vip_app': self.cleaned_data.get('vip_app'), 'priority_review': self.cleaned_data.get('priority_review'), } contact = self.cleaned_data.get('mozilla_contact') if contact is not None: updates['mozilla_contact'] = contact addon.update(**updates) 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