def get_actions(self): actions = SortedDict() actions['public'] = {'method': self.handler.process_public, 'minimal': False, 'label': _lazy('Push to public'), 'details': _lazy( 'This will approve the sandboxed app so it ' 'appears on the public side.')} actions['reject'] = {'method': self.handler.process_sandbox, 'label': _lazy('Reject'), 'minimal': False, 'details': _lazy( 'This will reject the app and remove it ' 'from the review queue.')} actions['info'] = {'method': self.handler.request_information, 'label': _lazy('Request more information'), 'minimal': True, 'details': _lazy( 'This will send the author(s) an email ' 'requesting more information.')} actions['super'] = {'method': self.handler.process_super_review, 'label': _lazy('Request super-review'), 'minimal': True, 'details': _lazy( 'Flag this app for an admin to review')} actions['comment'] = {'method': self.handler.process_comment, 'label': _lazy('Comment'), 'minimal': True, 'details': _lazy( 'Make a comment on this app. The ' 'author won\'t be able to see this.')} return actions
def group_tier_choices(self): """Creates tier choices with optgroups based on payment methods""" price_choices = [("free", _("Free (with in-app payments)"))] card_billed = [] operator_billed = [] card_and_operator_billed = [] for price in Price.objects.active(): choice = (price.pk, unicode(price)) # Special case price tier 0. if price.price == Decimal("0.00"): price_choices.append((price.pk, "%s (%s)" % (unicode(price), _("Promotional Pricing")))) # Tiers that can only be operator billed. elif price.method == PAYMENT_METHOD_OPERATOR: operator_billed.append(choice) # Tiers that can only be card billed. elif price.method == PAYMENT_METHOD_CARD: card_billed.append(choice) # Tiers that are can generally be billed by either # operator or card. elif price.method == PAYMENT_METHOD_ALL: card_and_operator_billed.append(choice) if operator_billed: price_choices.append((_lazy("Only supports carrier billing"), operator_billed)) if card_billed: price_choices.append((_lazy("Only supports credit-card billing"), card_billed)) if card_and_operator_billed: price_choices.append((_lazy("Supports all billing methods"), card_and_operator_billed)) return price_choices
def get_actions(self, request, addon): labels, details = self._review_actions() actions = SortedDict() if not addon.admin_review or acl.action_allowed(request, "ReviewerAdminTools", "View"): if self.review_type != "preliminary": actions["public"] = { "method": self.handler.process_public, "minimal": False, "label": _lazy("Push to public"), } actions["prelim"] = { "method": self.handler.process_preliminary, "label": labels["prelim"], "minimal": False, } actions["reject"] = {"method": self.handler.process_sandbox, "label": _lazy("Reject"), "minimal": False} actions["info"] = { "method": self.handler.request_information, "label": _lazy("Request more information"), "minimal": True, } actions["super"] = { "method": self.handler.process_super_review, "label": _lazy("Request super-review"), "minimal": True, } actions["comment"] = {"method": self.handler.process_comment, "label": _lazy("Comment"), "minimal": True} for k, v in actions.items(): v["details"] = details.get(k) return actions
def index(request): """ Display search results for Opinions on Firefox. Shows breakdown of Praise/Issues/Ideas, sites/themes, and search filters. If no search criteria are explicitly set, the page is considered the "Dashboard" (i.e. the home page of Firefox Input). Otherwise, the title of the page is "Search Results". """ VERSIONS = VersionCount.objects.filter(active=1) VERSION_CHOICES = { FIREFOX: ([('--', _lazy(u'-- all --', 'version_choice'))] + [(v.version, v.version) for v in VERSIONS if v.product == FIREFOX.id]), MOBILE: ([('--', _lazy(u'-- all --', 'version_choice'))] + [(v.version, v.version) for v in VERSIONS if v.product == MOBILE.id]), } try: meta = ('type', 'locale', 'platform', 'day_sentiment', 'manufacturer', 'device') (results, form, product, version, metas, type_filter) = _get_results( request, meta=meta) except SearchError, e: return render(request, 'search/unavailable.html', {'search_error': e}, status=500)
def __init__(self, *args, **kwargs): """Override the __init__ method to change form labels""" super(PasswordChangeForm, self).__init__(*args, **kwargs) self.fields['old_password'].label = _lazy(u'Verify your old password') self.fields['new_password1'].label = _lazy(u'Enter a new password') self.fields['new_password2'].label = _lazy(u'Confirm new password')
def get_actions(self): labels, details = self._review_actions() actions = SortedDict() if self.review_type != 'preliminary': actions['public'] = {'method': self.handler.process_public, 'minimal': False, 'label': _lazy('Push to public')} actions['prelim'] = {'method': self.handler.process_preliminary, 'label': labels['prelim'], 'minimal': False} actions['reject'] = {'method': self.handler.process_sandbox, 'label': _lazy('Reject'), 'minimal': False} actions['info'] = {'method': self.handler.request_information, 'label': _lazy('Request more information'), 'minimal': True} actions['super'] = {'method': self.handler.process_super_review, 'label': _lazy('Request super-review'), 'minimal': True} actions['comment'] = {'method': self.handler.process_comment, 'label': _lazy('Comment'), 'minimal': True} for k, v in actions.items(): v['details'] = details.get(k) return actions
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 get_actions(self): labels, details = self._review_actions() actions = SortedDict() if self.review_type != "preliminary": actions["public"] = { "method": self.handler.process_public, "minimal": False, "label": _lazy("Push to public"), } actions["prelim"] = {"method": self.handler.process_preliminary, "label": labels["prelim"], "minimal": False} actions["reject"] = {"method": self.handler.process_sandbox, "label": _lazy("Reject"), "minimal": False} actions["info"] = { "method": self.handler.request_information, "label": _lazy("Request more information"), "minimal": True, } actions["super"] = { "method": self.handler.process_super_review, "label": _lazy("Request super-review"), "minimal": True, } for k, v in actions.items(): v["details"] = details.get(k) return actions
def __init__(self, *args, **kwargs): """Override the __init__ method to change form labels""" super(PasswordChangeForm, self).__init__(*args, **kwargs) self.fields["old_password"].label = _lazy(u"Verify your old password") self.fields["new_password1"].label = _lazy(u"Enter a new password") self.fields["new_password2"].label = _lazy(u"Confirm new password")
def __init__(self, *args, **kwargs): super(ApiReviewersSearchForm, self).__init__(*args, **kwargs) # Mobile form, to render, expects choices from the Django field. BOOL_CHOICES = ((u"", _lazy("Unknown")), (u"true", _lazy("Yes")), (u"false", _lazy("No"))) for field_name, field in self.fields.iteritems(): if isinstance(field, forms.NullBooleanField): self.fields[field_name].choices = BOOL_CHOICES
def render_additional_info(self, row): info = [] if row.is_site_specific: info.append(_lazy(u'Site Specific')) if row.external_software: info.append(_lazy(u'Requires External Software')) if row.binary or row.binary_components: info.append(_lazy(u'Binary Components')) return u', '.join([jinja2.escape(i) for i in info])
def promo_video_dance(request): """Dancing promo video.""" d = dict(video_title=_lazy('Dance'), video_description=_lazy("He's got the moves, he's got ambition. " "How far can this fox's feet take him? " "Get inspired for your Firefox Flicks " "entry by checking out our video."), page_type='videos', video_embed=Markup(embedCode(promo_video_shortlink('dance'), width=600, height=337))) return render(request, 'videos/promo.html', d)
def __init__(self, *args, **kwargs): super(UsernameField, self).__init__( label=_lazy(u'Username'), max_length=30, min_length=3, regex=r'^[\w.@+-]+$', help_text=_lazy(u'Required. 30 characters or fewer. ' 'Letters, digits and @/./+/-/_ only.'), error_messages={'invalid': USERNAME_INVALID, 'required': USERNAME_REQUIRED, 'min_length': USERNAME_SHORT, 'max_length': USERNAME_LONG}, *args, **kwargs)
def promo_video_twilight(request): """Twilight parody promo video.""" desc = _lazy('A teenage girl learns the truth about the fox. Get inspired ' 'for your Firefox Flicks entry by checking out our video.') d = dict(video_title=_lazy('Twilight'), video_description=desc, tweet_text=desc, page_type='videos', video_embed=Markup(embedCode(promo_video_shortlink('twilight'), width=600, height=337))) return render(request, 'videos/promo.html', d)
def promo_video_noir(request): """Film Noir promo video.""" d = dict(video_title=_lazy('Noir'), video_description=_lazy('The fox meets a damsel in distress, but ' 'can he help her? Get inspired for your ' 'Firefox Flicks entry by checking out ' 'our video.'), page_type='videos', video_embed=Markup(embedCode(promo_video_shortlink('noir'), width=600, height=337))) return render(request, 'videos/promo.html', d)
def flags(self): props = ( ('admin_review', 'admin-review', _lazy('Admin Review')), ('is_jetpack', 'jetpack', _lazy('Jetpack Add-on')), ('is_traditional_restartless', 'restartless', _lazy('Restartless Add-on')), ('has_info_request', 'info', _lazy('More Information Requested')), ('has_editor_comment', 'editor', _lazy('Contains Editor Comment')), ) return [(cls, title) for (prop, cls, title) in props if getattr(self, prop)]
def __init__(self, *args, **kwargs): super(UsernameField, self).__init__( label=_lazy(u'Username'), max_length=30, min_length=3, regex=r'^[\w.-]+$', help_text=_lazy(u'Required. 30 characters or fewer. ' 'Letters, digits and ./-/_ only.'), widget=forms.TextInput(attrs={'placeholder': USERNAME_PLACEHOLDER}), error_messages={'invalid': USERNAME_INVALID, 'required': USERNAME_REQUIRED, 'min_length': USERNAME_SHORT, 'max_length': USERNAME_LONG}, *args, **kwargs)
def render_additional_info(self, row): info = [] if row.is_site_specific: info.append(_lazy(u"Site Specific")) if len(row.file_platform_ids) == 1 and row.file_platform_ids != [amo.PLATFORM_ALL.id]: k = row.file_platform_ids[0] # L10n: first argument is the platform such as Linux, Mac OS X info.append(_lazy(u"{0} only").format(amo.PLATFORMS[k].name)) if row.external_software: info.append(_lazy(u"Requires External Software")) if row.binary: info.append(_lazy(u"Binary Components")) return u", ".join([jinja2.escape(i) for i in info])
def data(self): topics = super(RootTopicSerializer, self).data return { 'subtopics': topics, 'documents': [], 'title': _lazy('All Topics'), 'slug': '', 'description': _lazy('All Topics'), 'parent': None, 'visible': True, 'product': None, 'path': '/', }
def validate_twitter(username): """Return a twitter username given '@' or http(s) strings.""" if username: username = re.sub('https?://(www\.)?twitter\.com/|@', '', username) # Twitter accounts must be alphanumeric ASCII including underscore, and <= 15 chars. # https://support.twitter.com/articles/101299-why-can-t-i-register-certain-usernames if len(username) > 15: raise ValidationError(_lazy('Twitter usernames cannot be longer than 15 characters.')) if not re.match('^\w+$', username): raise ValidationError(_lazy('Twitter usernames must contain only alphanumeric characters' ' and the underscore.')) return username
def search(request): to_json = JSONRenderer().render context = {} form = WikiSearchForm() # Get options to fill the various select boxes of the search forms. languages = _options_tuple_to_dict(form.fields['language'].choices) categories = _options_tuple_to_dict(form.fields['category'].choices) products = _options_tuple_to_dict(form.fields['product'].choices) topics = _options_tuple_to_dict(form.fields['topics'].choices) filters = { 'language': { 'meta': { 'name': 'language', 'label': _lazy(u'Language'), 'multi': False, }, 'options': languages, }, 'category': { 'meta': { 'name': 'category', 'label': _lazy(u'Category'), 'multi': True, }, 'options': categories, }, 'product': { 'meta': { 'name': 'product', 'label': _lazy(u'Relevant to'), 'multi': True, }, 'options': products, }, 'topics': { 'meta': { 'name': 'topics', 'label': _lazy(u'Topics'), 'multi': True, }, 'options': topics, }, } context['filters_json'] = to_json(filters) return render(request, 'coolsearch/search.html', context)
def locale_name(locale, native=False, default=_lazy('Unknown')): """Convert a locale code into a human readable locale name.""" if locale in product_details.languages: display_locale = 'native' if native else 'English' return product_details.languages[locale][display_locale] else: return default
def _mails(self, users_and_watches): revision = self.revision document = revision.document log.debug('Sending ready for review email for revision (id=%s)' % revision.id) subject = _lazy(u'{title} is ready for review ({creator})') url = reverse('wiki.review_revision', locale=document.locale, args=[document.slug, revision.id]) context = context_dict(revision) context['revision_url'] = add_utm(url, 'wiki-ready-review') context['locale'] = document.locale context['title'] = document.title context['creator'] = revision.creator context['comment'] = revision.comment users = [] for u, w in users_and_watches: if document.allows(u, 'review_revision'): users.append((u, w)) return email_utils.emails_with_users_and_watches( subject=subject, text_template='wiki/email/ready_for_review.ltxt', html_template='wiki/email/ready_for_review.html', context_vars=context, users_and_watches=users, default_locale=document.locale)
def datetimeformat(context, value, format='shortdatetime'): """ Returns date/time formatted using babel's locale settings. Uses the timezone from settings.py """ if not isinstance(value, datetime.datetime): # Expecting date value raise ValueError tzinfo = timezone(settings.TIME_ZONE) tzvalue = tzinfo.localize(value) locale = _babel_locale(_contextual_locale(context)) # If within a day, 24 * 60 * 60 = 86400s if format == 'shortdatetime': # Check if the date is today if value.toordinal() == datetime.date.today().toordinal(): formatted = _lazy(u'Today at %s') % format_time( tzvalue, format='short', locale=locale) else: formatted = format_datetime(tzvalue, format='short', locale=locale) elif format == 'longdatetime': formatted = format_datetime(tzvalue, format='long', locale=locale) elif format == 'date': formatted = format_date(tzvalue, locale=locale) elif format == 'time': formatted = format_time(tzvalue, locale=locale) elif format == 'datetime': formatted = format_datetime(tzvalue, locale=locale) else: # Unknown format raise DateTimeFormatError return jinja2.Markup('<time datetime="%s">%s</time>' % \ (tzvalue.isoformat(), formatted))
def reviewers_page_title(context, title=None, addon=None): if addon: title = u'%s | %s' % (title, addon.name) else: section = _lazy('Reviewer Tools') title = u'%s | %s' % (title, section) if title else section return mkt_page_title(context, title)
def __init__(self, *args, **kwargs): super(ReviewAppLogForm, self).__init__(*args, **kwargs) self.fields["search"].widget.attrs = { # L10n: Descript of what can be searched for. "placeholder": _lazy(u"app, reviewer, or comment"), "size": 30, }
def country_name(country, native=False, default=_lazy(u'Unknown')): """Convert a country code into a human readable country name""" if country in CODE_TO_COUNTRY: display_locale = 'native' if native else 'English' return CODE_TO_COUNTRY[country][display_locale] else: return default
def locale_name(locale, native=False, default=_lazy('Unknown')): """Convert a locale code into a human readable locale name.""" if locale in product_details.languages: return product_details.languages[locale][ native and 'native' or 'English'] else: return default
def datetimeformat(context, value, format='shortdatetime'): """ Returns a formatted date/time using Babel's locale settings. Uses the timezone from settings.py, if the user has not been authenticated. """ if not isinstance(value, datetime.datetime): # Expecting date value raise ValueError request = context.get('request') default_tzinfo = convert_tzinfo = timezone(settings.TIME_ZONE) if value.tzinfo is None: value = default_tzinfo.localize(value) new_value = value.astimezone(default_tzinfo) else: new_value = value if 'timezone' not in request.session: if request.user.is_authenticated(): try: convert_tzinfo = request.user.get_profile().timezone or \ default_tzinfo except (Profile.DoesNotExist, AttributeError): pass request.session['timezone'] = convert_tzinfo else: convert_tzinfo = request.session['timezone'] convert_value = new_value.astimezone(convert_tzinfo) locale = _babel_locale(_contextual_locale(context)) # If within a day, 24 * 60 * 60 = 86400s if format == 'shortdatetime': # Check if the date is today today = datetime.datetime.now(tz=convert_tzinfo).toordinal() if convert_value.toordinal() == today: formatted = _lazy(u'Today at %s') % format_time( convert_value, format='short', tzinfo=convert_tzinfo, locale=locale) else: formatted = format_datetime(convert_value, format='short', tzinfo=convert_tzinfo, locale=locale) elif format == 'longdatetime': formatted = format_datetime(convert_value, format='long', tzinfo=convert_tzinfo, locale=locale) elif format == 'date': formatted = format_date(convert_value, locale=locale) elif format == 'time': formatted = format_time(convert_value, tzinfo=convert_tzinfo, locale=locale) elif format == 'datetime': formatted = format_datetime(convert_value, tzinfo=convert_tzinfo, locale=locale) else: # Unknown format raise DateTimeFormatError return jinja2.Markup('<time datetime="%s">%s</time>' % \ (convert_value.isoformat(), formatted))
def _hook_video(self, parser, space, title): """Handles [[Video:video title]] with locale from parser.""" message = _lazy(u'The video "%s" does not exist.') % title # params, only modal supported for now title, params = build_hook_params(title, self.locale, VIDEO_PARAMS) # If this is a youtube video, return the youtube embed placeholder parsed_url = urlparse(title) netloc = parsed_url.netloc if netloc in ['youtu.be', 'youtube.com', 'www.youtube.com']: if netloc == 'youtu.be': # The video id is the path minus the leading / video_id = parsed_url.path[1:] else: # The video id is in the v= query param video_id = parse_qs(parsed_url.query)['v'][0] self.youtube_videos.append(video_id) return YOUTUBE_PLACEHOLDER % video_id v = get_object_fallback(Video, title, self.locale, message) if isinstance(v, basestring): return v return generate_video(v, params)
class ExternalAccount(models.Model): # Constants for type field values. TYPE_AMO = 'AMO' TYPE_BMO = 'BMO' TYPE_EMAIL = 'EMAIL' TYPE_GITHUB = 'GITHUB' TYPE_MDN = 'MDN' TYPE_SUMO = 'SUMO' TYPE_FACEBOOK = 'FACEBOOK' TYPE_TWITTER = 'TWITTER' TYPE_AIM = 'AIM' TYPE_GTALK = 'GTALK' TYPE_SKYPE = 'SKYPE' TYPE_YAHOO = 'YAHOO' TYPE_WEBSITE = 'WEBSITE' TYPE_BITBUCKET = 'BITBUCKET' TYPE_SLIDESHARE = 'SLIDESHARE' TYPE_WEBMAKER = 'WEBMAKER' TYPE_MOWIKI = 'MOZILLAWIKI' TYPE_REMO = 'REMO' TYPE_LINKEDIN = 'LINKEDIN' TYPE_JABBER = 'JABBER' TYPE_DISCOURSE = 'DISCOURSE' TYPE_LANYRD = 'LANYRD' TYPE_LANDLINE = 'Phone (Landline)' TYPE_MOBILE = 'Phone (Mobile)' TYPE_MOVERBATIM = 'MOZILLAVERBATIM' TYPE_MOLOCOMOTION = 'MOZILLALOCOMOTION' TYPE_MOLOCATION = 'MOZILLALOCATION' TYPE_TRANSIFEX = 'TRANSIFEX' # Account type field documentation: # name: The name of the service that this account belongs to. What # users see # url: If the service features profile pages for its users, then # this field should be a link to that profile page. User's # identifier should be replaced by the special string # {identifier}. # validator: Points to a function which will clean and validate # user's entry. Function should return the cleaned # data. ACCOUNT_TYPES = { TYPE_AMO: { 'name': 'Mozilla Add-ons', 'url': 'https://addons.mozilla.org/user/{identifier}/', 'validator': validate_username_not_url }, TYPE_BMO: { 'name': 'Bugzilla (BMO)', 'url': 'https://bugzilla.mozilla.org/user_profile?login={identifier}', 'validator': validate_username_not_url }, TYPE_EMAIL: { 'name': 'Alternate email address', 'url': '', 'validator': validate_email }, TYPE_GITHUB: { 'name': 'GitHub', 'url': 'https://github.com/{identifier}', 'validator': validate_username_not_url }, TYPE_BITBUCKET: { 'name': 'Bitbucket', 'url': 'https://bitbucket.org/{identifier}', 'validator': validate_username_not_url }, TYPE_MDN: { 'name': 'MDN', 'url': 'https://developer.mozilla.org/profiles/{identifier}', 'validator': validate_username_not_url }, TYPE_MOLOCATION: { 'name': 'Mozilla Location Service', 'url': 'https://location.services.mozilla.com/leaders#{identifier}', 'validator': validate_username_not_url }, TYPE_SUMO: { 'name': 'Mozilla Support', 'url': 'https://support.mozilla.org/user/{identifier}', 'validator': validate_username_not_url }, TYPE_FACEBOOK: { 'name': 'Facebook', 'url': 'https://www.facebook.com/{identifier}', 'validator': validate_username_not_url }, TYPE_TWITTER: { 'name': 'Twitter', 'url': 'https://twitter.com/{identifier}', 'validator': validate_twitter }, TYPE_AIM: { 'name': 'AIM', 'url': '' }, TYPE_GTALK: { 'name': 'Google+ Hangouts', 'url': '', 'validator': validate_email }, TYPE_SKYPE: { 'name': 'Skype', 'url': '' }, TYPE_SLIDESHARE: { 'name': 'SlideShare', 'url': 'http://www.slideshare.net/{identifier}', 'validator': validate_username_not_url }, TYPE_YAHOO: { 'name': 'Yahoo! Messenger', 'url': '' }, TYPE_WEBSITE: { 'name': 'Website URL', 'url': '', 'validator': validate_website }, TYPE_WEBMAKER: { 'name': 'Mozilla Webmaker', 'url': 'https://{identifier}.makes.org', 'validator': validate_username_not_url }, TYPE_MOWIKI: { 'name': 'Mozilla Wiki', 'url': 'https://wiki.mozilla.org/User:{identifier}', 'validator': validate_username_not_url }, TYPE_REMO: { 'name': 'Mozilla Reps', 'url': 'https://reps.mozilla.org/u/{identifier}/', 'validator': validate_username_not_url }, TYPE_LINKEDIN: { 'name': 'LinkedIn', 'url': '', 'validator': validate_website }, TYPE_JABBER: { 'name': 'XMPP/Jabber', 'url': '', 'validator': validate_email }, TYPE_DISCOURSE: { 'name': 'Mozilla Discourse', 'url': 'https://discourse.mozilla-community.org/users/{identifier}', 'validator': validate_username_not_url }, TYPE_LANYRD: { 'name': 'Lanyrd', 'url': 'http://lanyrd.com/profile/{identifier}/', 'validator': validate_username_not_url }, TYPE_LANDLINE: { 'name': 'Phone (Landline)', 'url': '', 'validator': validate_phone_number }, TYPE_MOBILE: { 'name': 'Phone (Mobile)', 'url': '', 'validator': validate_phone_number }, TYPE_MOVERBATIM: { 'name': 'Mozilla Verbatim', 'url': 'https://localize.mozilla.org/accounts/{identifier}/', 'validator': validate_username_not_url }, TYPE_MOLOCOMOTION: { 'name': 'Mozilla Locomotion', 'url': 'http://mozilla.locamotion.org/accounts/{identifier}/', 'validator': validate_username_not_url }, TYPE_TRANSIFEX: { 'name': 'Transifex', 'url': 'https://www.transifex.com/accounts/profile/{identifier}/', 'validator': validate_username_not_url }, } user = models.ForeignKey(UserProfile) identifier = models.CharField(max_length=255, verbose_name=_lazy(u'Account Username')) type = models.CharField( max_length=30, choices=sorted( [(k, v['name']) for (k, v) in ACCOUNT_TYPES.iteritems() if k != TYPE_EMAIL], key=lambda x: x[1]), verbose_name=_lazy(u'Account Type')) privacy = models.PositiveIntegerField(default=MOZILLIANS, choices=PRIVACY_CHOICES) class Meta: ordering = ['type'] unique_together = ('identifier', 'type', 'user') def get_identifier_url(self): url = self.ACCOUNT_TYPES[self.type]['url'].format( identifier=urlquote(self.identifier)) return iri_to_uri(url) def unique_error_message(self, model_class, unique_check): if model_class == type(self) and unique_check == ('identifier', 'type', 'user'): return _('You already have an account with this name and type.') else: return super(ExternalAccount, self).unique_error_message(model_class, unique_check)
class LicenseForm(AMOModelForm): builtin = forms.TypedChoiceField(choices=[], coerce=int, widget=forms.RadioSelect( attrs={'class': 'license'}, renderer=LicenseChoiceRadio)) name = forms.CharField(widget=TranslationTextInput(), label=_lazy(u"What is your license's name?"), required=False, initial=_('Custom License')) text = forms.CharField(widget=TranslationTextarea(), required=False, label=_lazy(u'Provide the text of your license.')) def __init__(self, *args, **kw): addon = kw.pop('addon', None) self.version = None if addon: qs = addon.versions.order_by('-version')[:1] self.version = qs[0] if qs else None if self.version: kw['instance'], kw['initial'] = self.version.license, None # Clear out initial data if it's a builtin license. if getattr(kw['instance'], 'builtin', None): kw['initial'] = {'builtin': kw['instance'].builtin} kw['instance'] = None super(LicenseForm, self).__init__(*args, **kw) cs = [(x.builtin, x) for x in License.objects.builtins().filter(on_form=True)] cs.append((License.OTHER, _('Other'))) self.fields['builtin'].choices = cs class Meta: model = License fields = ('builtin', 'name', 'text') def clean_name(self): name = self.cleaned_data['name'] return name.strip() or _('Custom License') def clean(self): data = self.cleaned_data if self.errors: return data elif data['builtin'] == License.OTHER and not data['text']: raise forms.ValidationError( _('License text is required when choosing Other.')) return data def get_context(self): """Returns a view context dict having keys license_urls, license_form, and license_other_val. """ license_urls = dict(License.objects.builtins().values_list( 'builtin', 'url')) return dict(license_urls=license_urls, version=self.version, license_form=self.version and self, license_other_val=License.OTHER) def save(self, *args, **kw): """Save all form data. This will only create a new license if it's not one of the builtin ones. Keyword arguments **log=True** Set to False if you do not want to log this action for display on the developer dashboard. """ log = kw.pop('log', True) changed = self.changed_data builtin = self.cleaned_data['builtin'] if builtin != License.OTHER: license = License.objects.get(builtin=builtin) else: # Save the custom license: license = super(LicenseForm, self).save(*args, **kw) if self.version: if changed or license != self.version.license: self.version.update(license=license) if log: amo.log(amo.LOG.CHANGE_LICENSE, license, self.version.addon) return license
class Meta: verbose_name = _lazy(u'registration profile') verbose_name_plural = _lazy(u'registration profiles')
class DocumentForm(forms.ModelForm): """Form to create/edit a document.""" title = StrippedCharField( min_length=1, max_length=255, widget=forms.TextInput(attrs={'placeholder': TITLE_PLACEHOLDER}), label=_lazy(u'Title:'), help_text=_lazy(u'Title of article'), error_messages={ 'required': TITLE_REQUIRED, 'min_length': TITLE_SHORT, 'max_length': TITLE_LONG }) slug = StrippedCharField(min_length=1, max_length=255, widget=forms.TextInput(), label=_lazy(u'Slug:'), help_text=_lazy(u'Article URL'), error_messages={ 'required': SLUG_REQUIRED, 'min_length': SLUG_SHORT, 'max_length': SLUG_LONG }) category = forms.ChoiceField( choices=Document.CATEGORIES, initial=10, # Required for non-translations, which is # enforced in Document.clean(). required=False, label=_lazy(u'Category:'), help_text=_lazy(u'Type of article'), widget=forms.HiddenInput()) parent_topic = forms.ModelChoiceField(queryset=Document.objects.all(), required=False, label=_lazy(u'Parent:')) locale = forms.CharField(widget=forms.HiddenInput()) def clean_slug(self): slug = self.cleaned_data['slug'] if slug == '': # Default to the title, if missing. slug = self.cleaned_data['title'] # "?", " ", quote disallowed in slugs altogether if '?' in slug or ' ' in slug or '"' in slug or "'" in slug: raise forms.ValidationError(SLUG_INVALID) # Pattern copied from urls.py if not re.compile(r'^[^\$]+$').match(slug): raise forms.ValidationError(SLUG_INVALID) # Guard against slugs that match urlpatterns for pat in RESERVED_SLUGS: if re.compile(pat).match(slug): raise forms.ValidationError(SLUG_INVALID) return slug class Meta: model = Document fields = ('title', 'slug', 'category', 'locale') def save(self, parent_doc, **kwargs): """Persist the Document form, and return the saved Document.""" doc = super(DocumentForm, self).save(commit=False, **kwargs) doc.parent = parent_doc if 'parent_topic' in self.cleaned_data: doc.parent_topic = self.cleaned_data['parent_topic'] doc.save() # not strictly necessary since we didn't change # any m2m data since we instantiated the doc self.save_m2m() return doc
from django.conf import settings from tower import ugettext_lazy as _lazy # WARNING: When adding a new app feature here also include a migration. # # WARNING: Order matters here. Don't re-order these or alphabetize them. If you # add new ones put them on the end. # # These are used to dynamically generate the field list for the AppFeatures # django model in mkt.webapps.models. APP_FEATURES = OrderedDict([ ('APPS', { 'name': _lazy(u'App Management API'), 'description': _lazy(u'The app requires the `navigator.mozApps` API ' u'to install and manage other apps.'), 'apis': ('navigator.mozApps',), }), ('PACKAGED_APPS', { 'name': _lazy(u'Packaged Apps Install API'), 'description': _lazy( u'The app requires the `navigator.mozApps.installPackage` API ' u'to install other packaged apps.'), 'apis': ('navigator.mozApps.installPackage',), }), ('PAY', { 'name': _lazy(u'Web Payment'), 'description': _lazy(u'The app requires the `navigator.mozApps` API.'), 'apis': ('navigator.pay', 'navigator.mozPay',),
@classmethod def unstrip_fors(cls, html, dehydrations): """Replace the tokens with <for> tags the ForParser understands.""" def hydrate(match): return dehydrations.get(int(match.group(1) or match.group(2)), '') # Put <for ...> tags back in: html = cls._PARSED_STRIPPED_FOR.sub(hydrate, html) # Replace {/for} tags: return cls._PARSED_STRIPPED_FOR_CLOSER.sub(u'</for>', html) # L10n: This error is displayed if a template is included into itself. RECURSION_MESSAGE = _lazy(u'[Recursive inclusion of "%s"]') class WikiParser(sumo_parser.WikiParser): """An extension of the parser from the forums adding more crazy features {for} tags, inclusions, and templates--oh my! """ image_template = 'wikiparser/hook_image_lazy.html' def __init__(self, base_url=None, doc_id=None): """ doc_id -- If you want to be nice, pass the ID of the Document you are rendering. This will make recursive inclusions fail immediately
class AddonFilter(BaseFilter): opts = (('name', _lazy(u'Name')), ('updated', _lazy(u'Updated')), ('created', _lazy(u'Created')), ('popular', _lazy(u'Downloads')), ('rating', _lazy(u'Rating')))
class AppFilter(BaseFilter): opts = (('name', _lazy(u'Name')), ('created', _lazy(u'Created')))
class UserRegisterForm(happyforms.ModelForm, UsernameMixin, PasswordMixin): """ For registering users. We're not building off d.contrib.auth.forms.UserCreationForm because it doesn't do a lot of the details here, so we'd have to rewrite most of it anyway. """ username = forms.CharField(max_length=50, widget=RequiredTextInput) email = forms.EmailField(widget=RequiredEmailInput) display_name = forms.CharField(label=_lazy(u'Display Name'), max_length=50, required=False) location = forms.CharField(label=_lazy(u'Location'), max_length=100, required=False) occupation = forms.CharField(label=_lazy(u'Occupation'), max_length=100, required=False) password = forms.CharField(max_length=255, min_length=PasswordMixin.min_length, error_messages=PasswordMixin.error_msg, widget=PasswordMixin.widget(render_value=False, required=True)) password2 = forms.CharField(max_length=255, widget=PasswordMixin.widget(render_value=False, required=True)) recaptcha = captcha.fields.ReCaptchaField() homepage = forms.URLField(label=_lazy(u'Homepage'), required=False) class Meta: model = UserProfile fields = ('username', 'display_name', 'location', 'occupation', 'password', 'password2', 'recaptcha', 'homepage', 'email') def __init__(self, *args, **kwargs): super(UserRegisterForm, self).__init__(*args, **kwargs) if not settings.RECAPTCHA_PRIVATE_KEY: del self.fields['recaptcha'] errors = { 'invalid': _('This URL has an invalid format. ' 'Valid URLs look like ' 'http://example.com/my_page.') } self.fields['homepage'].error_messages = errors def clean_email(self): d = self.cleaned_data['email'].split('@')[-1] if BlacklistedEmailDomain.blocked(d): raise forms.ValidationError( _('Please use an email address from a ' 'different provider to complete ' 'your registration.')) return self.cleaned_data['email'] def clean_display_name(self): name = self.cleaned_data['display_name'] if BlacklistedName.blocked(name): raise forms.ValidationError(_('This display name cannot be used.')) return name def clean(self): super(UserRegisterForm, self).clean() data = self.cleaned_data # Passwords p1 = data.get('password') p2 = data.get('password2') # If p1 is invalid because its blocked, this message is non sensical. if p1 and p1 != p2: msg = _('The passwords did not match.') self._errors['password2'] = ErrorList([msg]) if p2: del data['password2'] return data
# From page 10 of the Mozilla Exporter API docs v1.0.0 # # BDT not in docs, but added in for bug 1043481. BANGO_CURRENCIES = [ 'AUD', 'BDT', 'CAD', 'CHF', 'COP', 'DKK', 'EGP', 'EUR', 'GBP', 'IDR', 'MXN', 'MYR', 'NOK', 'NZD', 'PHP', 'PLN', 'QAR', 'SEK', 'SGD', 'THB', 'USD', 'ZAR' ] BANGO_CURRENCIES = dict((k, ALL_CURRENCIES[k]) for k in BANGO_CURRENCIES) BANGO_OUTPAYMENT_CURRENCIES = ['EUR', 'GBP', 'USD'] BANGO_OUTPAYMENT_CURRENCIES = [(k, ALL_CURRENCIES[k]) for k in BANGO_OUTPAYMENT_CURRENCIES] BANGO_COUNTRIES = [ ('AFG', _lazy(u'Afghanistan')), ('ALA', _lazy(u'Åland Islands')), ('ALB', _lazy(u'Albania')), ('DZA', _lazy(u'Algeria')), ('ASM', _lazy(u'American Samoa')), ('AND', _lazy(u'Andorra')), ('AGO', _lazy(u'Angola')), ('AIA', _lazy(u'Anguilla')), ('ATA', _lazy(u'Antarctica')), ('ATG', _lazy(u'Antigua and Barbuda')), ('ARG', _lazy(u'Argentina')), ('ARM', _lazy(u'Armenia')), ('ABW', _lazy(u'Aruba')), ('AUS', _lazy(u'Australia')), ('AUT', _lazy(u'Austria')), ('AZE', _lazy(u'Azerbaijan')),
def yesno(boolean_value): return jinja2.Markup(_lazy(u'Yes') if boolean_value else _lazy(u'No'))
class RegionForm(forms.Form): regions = forms.MultipleChoiceField( required=False, label=_lazy(u'Choose the regions your app will be listed in:'), choices=mkt.regions.REGIONS_CHOICES_NAME, widget=forms.CheckboxSelectMultiple, error_messages={ 'required': _lazy(u'You must select at least one region.') }) special_regions = forms.MultipleChoiceField( required=False, choices=[(x.id, x.name) for x in mkt.regions.SPECIAL_REGIONS], widget=forms.CheckboxSelectMultiple) enable_new_regions = forms.BooleanField(required=False, label=_lazy(u'Enable new regions')) restricted = forms.TypedChoiceField( required=False, choices=[(0, _lazy('Make my app available in most regions')), (1, _lazy('Choose where my app is made available'))], widget=forms.RadioSelect(attrs={'class': 'choices'}), initial=0, coerce=int) def __init__(self, *args, **kw): self.product = kw.pop('product', None) self.request = kw.pop('request', None) super(RegionForm, self).__init__(*args, **kw) # If we have excluded regions, uncheck those. # Otherwise, default to everything checked. self.regions_before = self.product.get_region_ids(restofworld=True) self.initial = { 'regions': sorted(self.regions_before), 'restricted': int(self.product.geodata.restricted), 'enable_new_regions': self.product.enable_new_regions, } # The checkboxes for special regions are # # - checked ... if an app has not been requested for approval in # China or the app has been rejected in China. # # - unchecked ... if an app has been requested for approval in # China or the app has been approved in China. unchecked_statuses = (amo.STATUS_NULL, amo.STATUS_REJECTED) for region in self.special_region_objs: if self.product.geodata.get_status(region) in unchecked_statuses: # If it's rejected in this region, uncheck its checkbox. if region.id in self.initial['regions']: self.initial['regions'].remove(region.id) elif region.id not in self.initial['regions']: # If it's pending/public, check its checkbox. self.initial['regions'].append(region.id) @property def regions_by_id(self): return mkt.regions.REGIONS_CHOICES_ID_DICT @property def special_region_objs(self): return mkt.regions.SPECIAL_REGIONS @property def special_region_ids(self): return mkt.regions.SPECIAL_REGION_IDS @property def special_region_statuses(self): """Returns the null/pending/public status for each region.""" statuses = {} for region in self.special_region_objs: statuses[region.id] = self.product.geodata.get_status_slug(region) return statuses @property def special_region_messages(self): """Returns the L10n messages for each region's status.""" return self.product.geodata.get_status_messages() def is_toggling(self): if not self.request or not hasattr(self.request, 'POST'): return False value = self.request.POST.get('toggle-paid') return value if value in ('free', 'paid') else False def _product_is_paid(self): return (self.product.premium_type in amo.ADDON_PREMIUMS or self.product.premium_type == amo.ADDON_FREE_INAPP) def clean_regions(self): regions = self.cleaned_data['regions'] if not self.is_toggling(): if not regions: raise forms.ValidationError( _('You must select at least one region.')) return regions def save(self): # Don't save regions if we are toggling. if self.is_toggling(): return regions = [int(x) for x in self.cleaned_data['regions']] special_regions = [ int(x) for x in self.cleaned_data['special_regions'] ] restricted = int(self.cleaned_data['restricted'] or 0) if restricted: before = set(self.regions_before) after = set(regions) log.info(u'[Webapp:%s] App mark as restricted.' % self.product) # Add new region exclusions. to_add = before - after for region in to_add: aer, created = self.product.addonexcludedregion.get_or_create( region=region) if created: log.info(u'[Webapp:%s] Excluded from new region (%s).' % (self.product, region)) # Remove old region exclusions. to_remove = after - before for region in to_remove: self.product.addonexcludedregion.filter(region=region).delete() log.info(u'[Webapp:%s] No longer exluded from region (%s).' % (self.product, region)) else: self.product.addonexcludedregion.all().delete() log.info(u'[Webapp:%s] App mark as unrestricted.' % self.product) self.product.geodata.update(restricted=restricted) # Toggle region exclusions/statuses for special regions (e.g., China). toggle_app_for_special_regions(self.request, self.product, special_regions) if self.cleaned_data['enable_new_regions']: self.product.update(enable_new_regions=True) log.info(u'[Webapp:%s] will be added to future regions.' % self.product) else: self.product.update(enable_new_regions=False) log.info(u'[Webapp:%s] will not be added to future regions.' % self.product)
import jingo from caching.base import CachingManager, CachingMixin from funfactory.manage import path from funfactory.urlresolvers import reverse from jinja2 import Markup from tower import ugettext_lazy as _lazy from badges.models import Badge, BadgeInstance from banners import COLOR_CHOICES from shared.models import LocaleField, ModelBase from shared.storage import OverwritingStorage from shared.utils import (absolutify, product_languages_lower, ugettext_locale as _locale) # L10n: Width and height are the width and height of an image. SIZE = _lazy('%(width)sx%(height)s pixels') BANNER_TEMPLATE_FILE = 'apps/banners/templates/banners/banner_template.html' with open(path(BANNER_TEMPLATE_FILE)) as f: BANNER_TEMPLATE = f.read() def rename(instance, filename): props = '%d_%s_%s_%s_%s' % (instance.banner_id, instance.image.width, instance.image.height, instance.color, instance.locale) hash = hashlib.sha1(props).hexdigest() extension = os.path.splitext(filename)[1] name = '%s%s' % (hash, extension) return os.path.join(settings.BANNER_IMAGE_PATH, name)
class UserProfilePrivacyModel(models.Model): _privacy_level = None privacy_photo = PrivacyField() privacy_full_name = PrivacyField() privacy_ircname = PrivacyField() privacy_email = PrivacyField() privacy_bio = PrivacyField() privacy_geo_city = PrivacyField() privacy_geo_region = PrivacyField() privacy_geo_country = PrivacyField() privacy_groups = PrivacyField() privacy_skills = PrivacyField() privacy_languages = PrivacyField() privacy_date_mozillian = PrivacyField() privacy_timezone = PrivacyField() privacy_tshirt = PrivacyField(choices=((PRIVILEGED, _lazy(u'Privileged')), ), default=PRIVILEGED) privacy_title = PrivacyField() privacy_story_link = PrivacyField() CACHED_PRIVACY_FIELDS = None class Meta: abstract = True @classmethod def clear_privacy_fields_cache(cls): """ Clear any caching of the privacy fields. (This is only used in testing.) """ cls.CACHED_PRIVACY_FIELDS = None @classmethod def privacy_fields(cls): """ Return a dictionary whose keys are the names of the fields in this model that are privacy-controlled, and whose values are the default values to use for those fields when the user is not privileged to view their actual value. """ # Cache on the class object if cls.CACHED_PRIVACY_FIELDS is None: privacy_fields = {} field_names = cls._meta.get_all_field_names() for name in field_names: if name.startswith( 'privacy_') or not 'privacy_%s' % name in field_names: # skip privacy fields and uncontrolled fields continue field = cls._meta.get_field(name) # Okay, this is a field that is privacy-controlled # Figure out a good default value for it (to show to users # who aren't privileged to see the actual value) if isinstance(field, ManyToManyField): default = field.related.parent_model.objects.none() else: default = field.get_default() privacy_fields[name] = default # HACK: There's not really an email field on UserProfile, # but it's faked with a property privacy_fields['email'] = u'' cls.CACHED_PRIVACY_FIELDS = privacy_fields return cls.CACHED_PRIVACY_FIELDS
class UserProfile(UserProfilePrivacyModel): REFERRAL_SOURCE_CHOICES = ( ('direct', 'Mozillians'), ('contribute', 'Get Involved'), ) objects = UserProfileManager() user = models.OneToOneField(User) full_name = models.CharField(max_length=255, default='', blank=False, verbose_name=_lazy(u'Full Name')) is_vouched = models.BooleanField( default=False, help_text='You can edit vouched status by editing invidual vouches') can_vouch = models.BooleanField( default=False, help_text='You can edit can_vouch status by editing invidual vouches') last_updated = models.DateTimeField(auto_now=True, default=datetime.now) groups = models.ManyToManyField(Group, blank=True, related_name='members', through=GroupMembership) skills = models.ManyToManyField(Skill, blank=True, related_name='members') bio = models.TextField(verbose_name=_lazy(u'Bio'), default='', blank=True) photo = ImageField(default='', blank=True, upload_to=_calculate_photo_filename) ircname = models.CharField(max_length=63, verbose_name=_lazy(u'IRC Nickname'), default='', blank=True) # validated geo data (validated that it's valid geo data, not that the # mozillian is there :-) ) geo_country = models.ForeignKey('geo.Country', blank=True, null=True, on_delete=models.SET_NULL) geo_region = models.ForeignKey('geo.Region', blank=True, null=True, on_delete=models.SET_NULL) geo_city = models.ForeignKey('geo.City', blank=True, null=True, on_delete=models.SET_NULL) lat = models.FloatField(_lazy(u'Latitude'), blank=True, null=True) lng = models.FloatField(_lazy(u'Longitude'), blank=True, null=True) allows_community_sites = models.BooleanField( default=True, verbose_name=_lazy(u'Sites that can determine my vouched status'), choices=((True, _lazy(u'All Community Sites')), (False, _lazy(u'Only Mozilla Properties')))) allows_mozilla_sites = models.BooleanField( default=True, verbose_name=_lazy(u'Allow Mozilla sites to access my profile data?'), choices=((True, _lazy(u'Yes')), (False, _lazy(u'No')))) basket_token = models.CharField(max_length=1024, default='', blank=True) date_mozillian = models.DateField('When was involved with Mozilla', null=True, blank=True, default=None) timezone = models.CharField(max_length=100, blank=True, default='', choices=zip(common_timezones, common_timezones)) tshirt = models.IntegerField( _lazy(u'T-Shirt'), blank=True, null=True, default=None, choices=((1, _lazy(u'Fitted Small')), (2, _lazy(u'Fitted Medium')), (3, _lazy(u'Fitted Large')), (4, _lazy(u'Fitted X-Large')), (5, _lazy(u'Fitted XX-Large')), (6, _lazy(u'Fitted XXX-Large')), (7, _lazy(u'Straight-cut Small')), (8, _lazy(u'Straight-cut Medium')), (9, _lazy(u'Straight-cut Large')), (10, _lazy(u'Straight-cut X-Large')), (11, _lazy(u'Straight-cut XX-Large')), (12, _lazy(u'Straight-cut XXX-Large')))) title = models.CharField(_lazy(u'What do you do for Mozilla?'), max_length=70, blank=True, default='') story_link = models.URLField( _lazy(u'Link to your contribution story'), help_text=_lazy(u'If you have created something public that ' u'tells the story of how you came to be a ' u'Mozillian, specify that link here.'), max_length=1024, blank=True, default='') referral_source = models.CharField(max_length=32, choices=REFERRAL_SOURCE_CHOICES, default='direct') def __unicode__(self): """Return this user's name when their profile is called.""" return self.display_name def get_absolute_url(self): return reverse('phonebook:profile_view', args=[self.user.username]) class Meta: db_table = 'profile' ordering = ['full_name'] def __getattribute__(self, attrname): """Special privacy aware __getattribute__ method. This method returns the real value of the attribute of object, if the privacy_level of the attribute is at least as large as the _privacy_level attribute. Otherwise it returns a default privacy respecting value for the attribute, as defined in the privacy_fields dictionary. special_functions provides methods that privacy safe their respective properties, where the privacy modifications are more complex. """ _getattr = (lambda x: super(UserProfile, self).__getattribute__(x)) privacy_fields = UserProfile.privacy_fields() privacy_level = _getattr('_privacy_level') special_functions = { 'accounts': '_accounts', 'alternate_emails': '_alternate_emails', 'email': '_primary_email', 'is_public_indexable': '_is_public_indexable', 'languages': '_languages', 'vouches_made': '_vouches_made', 'vouches_received': '_vouches_received', 'vouched_by': '_vouched_by', 'websites': '_websites' } if attrname in special_functions: return _getattr(special_functions[attrname]) if not privacy_level or attrname not in privacy_fields: return _getattr(attrname) field_privacy = _getattr('privacy_%s' % attrname) if field_privacy < privacy_level: return privacy_fields.get(attrname) return _getattr(attrname) def _filter_accounts_privacy(self, accounts): if self._privacy_level: return accounts.filter(privacy__gte=self._privacy_level) return accounts @property def _accounts(self): _getattr = (lambda x: super(UserProfile, self).__getattribute__(x)) excluded_types = [ ExternalAccount.TYPE_WEBSITE, ExternalAccount.TYPE_EMAIL ] accounts = _getattr('externalaccount_set').exclude( type__in=excluded_types) return self._filter_accounts_privacy(accounts) @property def _alternate_emails(self): _getattr = (lambda x: super(UserProfile, self).__getattribute__(x)) accounts = _getattr('externalaccount_set').filter( type=ExternalAccount.TYPE_EMAIL) return self._filter_accounts_privacy(accounts) @property def _is_public_indexable(self): for field in PUBLIC_INDEXABLE_FIELDS: if getattr(self, field, None) and getattr( self, 'privacy_%s' % field, None) == PUBLIC: return True return False @property def _languages(self): _getattr = (lambda x: super(UserProfile, self).__getattribute__(x)) if self._privacy_level > _getattr('privacy_languages'): return _getattr('language_set').none() return _getattr('language_set').all() @property def _primary_email(self): _getattr = (lambda x: super(UserProfile, self).__getattribute__(x)) privacy_fields = UserProfile.privacy_fields() if self._privacy_level and _getattr( 'privacy_email') < self._privacy_level: email = privacy_fields['email'] return email return _getattr('user').email @property def _vouched_by(self): privacy_level = self._privacy_level voucher = (UserProfile.objects.filter( vouches_made__vouchee=self).order_by('vouches_made__date')) if voucher.exists(): voucher = voucher[0] if privacy_level: voucher.set_instance_privacy_level(privacy_level) for field in UserProfile.privacy_fields(): if getattr(voucher, 'privacy_%s' % field) >= privacy_level: return voucher return None return voucher return None def _vouches(self, type): _getattr = (lambda x: super(UserProfile, self).__getattribute__(x)) vouch_ids = [] for vouch in _getattr(type).all(): vouch.vouchee.set_instance_privacy_level(self._privacy_level) for field in UserProfile.privacy_fields(): if getattr(vouch.vouchee, 'privacy_%s' % field, 0) >= self._privacy_level: vouch_ids.append(vouch.id) vouches = _getattr(type).filter(pk__in=vouch_ids) return vouches @property def _vouches_made(self): _getattr = (lambda x: super(UserProfile, self).__getattribute__(x)) if self._privacy_level: return self._vouches('vouches_made') return _getattr('vouches_made') @property def _vouches_received(self): _getattr = (lambda x: super(UserProfile, self).__getattribute__(x)) if self._privacy_level: return self._vouches('vouches_received') return _getattr('vouches_received') @property def _websites(self): _getattr = (lambda x: super(UserProfile, self).__getattribute__(x)) accounts = _getattr('externalaccount_set').filter( type=ExternalAccount.TYPE_WEBSITE) return self._filter_accounts_privacy(accounts) @property def display_name(self): return self.full_name @property def privacy_level(self): """Return user privacy clearance.""" if (self.user.groups.filter(name='Managers').exists() or self.user.is_superuser): return PRIVILEGED if self.groups.filter(name='staff').exists(): return EMPLOYEES if self.is_vouched: return MOZILLIANS return PUBLIC @property def is_complete(self): """Tests if a user has all the information needed to move on past the original registration view. """ return self.display_name.strip() != '' @property def is_public(self): """Return True is any of the privacy protected fields is PUBLIC.""" for field in type(self).privacy_fields(): if getattr(self, 'privacy_%s' % field, None) == PUBLIC: return True return False @property def is_manager(self): return self.user.is_superuser or self.user.groups.filter( name='Managers').exists() @property def date_vouched(self): """ Return the date of the first vouch, if available.""" vouches = self.vouches_received.all().order_by('date')[:1] if vouches: return vouches[0].date return None def set_instance_privacy_level(self, level): """Sets privacy level of instance.""" self._privacy_level = level def set_privacy_level(self, level, save=True): """Sets all privacy enabled fields to 'level'.""" for field in type(self).privacy_fields(): setattr(self, 'privacy_%s' % field, level) if save: self.save() def set_membership(self, model, membership_list): """Alters membership to Groups and Skills.""" if model is Group: m2mfield = self.groups alias_model = GroupAlias elif model is Skill: m2mfield = self.skills alias_model = SkillAlias # Remove any visible groups that weren't supplied in this list. if model is Group: GroupMembership.objects.filter(userprofile=self, group__visible=True)\ .exclude(group__name__in=membership_list).delete() else: m2mfield.remove(*[ g for g in m2mfield.all() if g.name not in membership_list and g.is_visible ]) # Add/create the rest of the groups groups_to_add = [] for g in membership_list: if alias_model.objects.filter(name=g).exists(): group = alias_model.objects.get(name=g).alias else: group = model.objects.create(name=g) if group.is_visible: groups_to_add.append(group) if model is Group: for group in groups_to_add: group.add_member(self) else: m2mfield.add(*groups_to_add) def get_photo_thumbnail(self, geometry='160x160', **kwargs): if 'crop' not in kwargs: kwargs['crop'] = 'center' if self.photo: return get_thumbnail(self.photo, geometry, **kwargs) return get_thumbnail(settings.DEFAULT_AVATAR_PATH, geometry, **kwargs) def get_photo_url(self, geometry='160x160', **kwargs): """Return photo url. If privacy allows and no photo set, return gravatar link. If privacy allows and photo set return local photo link. If privacy doesn't allow return default local link. """ privacy_level = getattr(self, '_privacy_level', MOZILLIANS) if (not self.photo and self.privacy_photo >= privacy_level): return gravatar(self.user.email, size=geometry) return self.get_photo_thumbnail(geometry, **kwargs).url def is_vouchable(self, voucher): """Check whether self can receive a vouch from voucher.""" # If there's a voucher, they must be able to vouch. if voucher and not voucher.can_vouch: return False # Maximum VOUCH_COUNT_LIMIT vouches per account, no matter what. if self.vouches_received.all().count() >= settings.VOUCH_COUNT_LIMIT: return False # If you've already vouched this account, you cannot do it again vouch_query = self.vouches_received.filter(voucher=voucher) if voucher and vouch_query.exists(): return False return True def vouch(self, vouched_by, description='', autovouch=False): if not self.is_vouchable(vouched_by): return vouch = self.vouches_received.create(voucher=vouched_by, date=datetime.now(), description=description, autovouch=autovouch) self._email_now_vouched(vouched_by, description) return vouch def auto_vouch(self): """Auto vouch mozilla.com users.""" email = self.user.email if any(email.endswith('@' + x) for x in settings.AUTO_VOUCH_DOMAINS): if not self.vouches_received.filter( description=settings.AUTO_VOUCH_REASON, autovouch=True).exists(): self.vouch(None, settings.AUTO_VOUCH_REASON, autovouch=True) def _email_now_vouched(self, vouched_by, description=''): """Email this user, letting them know they are now vouched.""" name = None voucher_profile_link = None vouchee_profile_link = utils.absolutify(self.get_absolute_url()) if vouched_by: name = vouched_by.full_name voucher_profile_link = utils.absolutify( vouched_by.get_absolute_url()) number_of_vouches = self.vouches_received.all().count() template = get_template( 'phonebook/emails/vouch_confirmation_email.txt') message = template.render({ 'voucher_name': name, 'voucher_profile_url': voucher_profile_link, 'vouchee_profile_url': vouchee_profile_link, 'vouch_description': description, 'functional_areas_url': utils.absolutify(reverse('groups:index_functional_areas')), 'groups_url': utils.absolutify(reverse('groups:index_groups')), 'first_vouch': number_of_vouches == 1, 'can_vouch_threshold': number_of_vouches == settings.CAN_VOUCH_THRESHOLD, }) subject = _(u'You have been vouched on Mozillians.org') filtered_message = message.replace('"', '"').replace(''', "'") send_mail(subject, filtered_message, settings.FROM_NOREPLY, [self.user.email]) def lookup_basket_token(self): """ Query Basket for this user's token. If Basket doesn't find the user, returns None. If Basket does find the token, returns it. Otherwise, there must have been some error from the network or basket, and this method just lets that exception propagate so the caller can decide how best to handle it. (Does not update the token field on the UserProfile.) """ try: result = basket.lookup_user(email=self.user.email) except basket.BasketException as exception: if exception.code == basket.errors.BASKET_UNKNOWN_EMAIL: return None raise return result['token'] def get_annotated_groups(self): """ Return a list of all the visible groups the user is a member of or pending membership. The groups pending membership will have a .pending attribute set to True, others will have it set False. """ groups = [] # Query this way so we only get the groups that the privacy controls allow the # current user to see. We have to force evaluation of this query first, otherwise # Django combines the whole thing into one query and loses the privacy control. groups_manager = self.groups # checks to avoid AttributeError exception b/c self.groups may returns # EmptyQuerySet instead of the default manager due to privacy controls if hasattr(groups_manager, 'visible'): user_group_ids = list(groups_manager.visible().values_list( 'id', flat=True)) else: user_group_ids = [] for membership in self.groupmembership_set.filter( group_id__in=user_group_ids): group = membership.group group.pending = (membership.status == GroupMembership.PENDING) groups.append(group) return groups def timezone_offset(self): """ Return minutes the user's timezone is offset from UTC. E.g. if user is 4 hours behind UTC, returns -240. If user has not set a timezone, returns None (not 0). """ if self.timezone: return offset_of_timezone(self.timezone) def save(self, *args, **kwargs): self._privacy_level = None super(UserProfile, self).save(*args, **kwargs) # Auto_vouch follows the first save, because you can't # create foreign keys without a database id. self.auto_vouch() def reverse_geocode(self): """ Use the user's lat and lng to set their city, region, and country. Does not save the profile. """ if self.lat is None or self.lng is None: return from mozillians.geo.models import Country from mozillians.geo.lookup import reverse_geocode, GeoLookupException try: result = reverse_geocode(self.lat, self.lng) except GeoLookupException: if self.geo_country: # If self.geo_country is already set, just give up. pass else: # No country set, we need to at least set the placeholder one. self.geo_country = Country.objects.get(mapbox_id='geo_error') self.geo_region = None self.geo_city = None else: if result: country, region, city = result self.geo_country = country self.geo_region = region self.geo_city = city else: logger.error('Got back NONE from reverse_geocode on %s, %s' % (self.lng, self.lat))
from tower import ugettext as _, ugettext_lazy as _lazy from kitsune import search as constants from kitsune.forums.models import Forum, ThreadMappingType from kitsune.products.models import Product from kitsune.questions.models import QuestionMappingType from kitsune.search.models import get_mapping_types from kitsune.search.utils import locale_or_default, clean_excerpt, ComposedList from kitsune.search import es_utils from kitsune.search.forms import SearchForm from kitsune.search.es_utils import ES_EXCEPTIONS, F, AnalyzerS from kitsune.sumo.utils import paginate, smart_int from kitsune.wiki.facets import documents_for from kitsune.wiki.models import DocumentMappingType EXCERPT_JOINER = _lazy(u'...', 'between search excerpts') def jsonp_is_valid(func): func_regex = re.compile( r""" ^[a-zA-Z_\$] [a-zA-Z0-9_\$]* (\[[a-zA-Z0-9_\$]*\])* (\.[a-zA-Z0-9_\$]+ (\[[a-zA-Z0-9_\$]*\])* )*$ """, re.VERBOSE) return func_regex.match(func)
from django.db.models import Q, Manager, get_model from django.db.models.query import QuerySet, ValuesQuerySet from tower import ugettext_lazy as _lazy PRIVILEGED = 1 EMPLOYEES = 2 MOZILLIANS = 3 PUBLIC = 4 PRIVACY_CHOICES = ((MOZILLIANS, _lazy(u'Mozillians')), (PUBLIC, _lazy(u'Public'))) PUBLIC_INDEXABLE_FIELDS = ['full_name', 'ircname', 'email'] class UserProfileValuesQuerySet(ValuesQuerySet): """Custom ValuesQuerySet to support privacy. Note that when you specify fields in values() you need to include the related privacy field in your query. E.g. .values('first_name', 'privacy_first_name') """ def _clone(self, *args, **kwargs): c = super(UserProfileValuesQuerySet, self)._clone(*args, **kwargs) c._privacy_level = getattr(self, '_privacy_level', None) return c def iterator(self):
def translate(request, document_slug, revision_id=None): """Create a new translation of a wiki document. * document_slug is for the default locale * translation is to the request locale """ # TODO: Refactor this view into two views? (new, edit) # That might help reduce the headache-inducing branchiness. parent_doc = get_object_or_404(Document, locale=settings.WIKI_DEFAULT_LANGUAGE, slug=document_slug) user = request.user if settings.WIKI_DEFAULT_LANGUAGE == request.locale: # Don't translate to the default language. return HttpResponseRedirect( reverse('wiki.edit_document', locale=settings.WIKI_DEFAULT_LANGUAGE, args=[parent_doc.slug])) if not parent_doc.is_localizable: message = _lazy(u'You cannot translate this document.') return jingo.render(request, 'handlers/400.html', {'message': message}, status=400) based_on_rev = get_current_or_latest_revision(parent_doc, reviewed_only=False) disclose_description = bool(request.GET.get('opendescription')) try: doc = parent_doc.translations.get(locale=request.locale) except Document.DoesNotExist: doc = None disclose_description = True user_has_doc_perm = ((not doc) or (doc and doc.allows_editing_by(user))) user_has_rev_perm = ((not doc) or (doc and doc.allows_revision_by(user))) if not user_has_doc_perm and not user_has_rev_perm: # User has no perms, bye. raise PermissionDenied doc_form = rev_form = None base_rev = None if user_has_doc_perm: doc_initial = _document_form_initial(doc) if doc else None doc_form = DocumentForm( initial=doc_initial, can_create_tags=user.has_perm('taggit.add_tag')) if user_has_rev_perm: initial = {'based_on': based_on_rev.id, 'comment': ''} if revision_id: base_rev = Revision.objects.get(pk=revision_id) initial.update(content=base_rev.content, summary=base_rev.summary, keywords=base_rev.keywords) elif not doc: initial.update(content=based_on_rev.content, summary=based_on_rev.summary, keywords=based_on_rev.keywords) instance = doc and get_current_or_latest_revision(doc) rev_form = RevisionForm(instance=instance, initial=initial) base_rev = base_rev or instance if request.method == 'POST': which_form = request.POST.get('form', 'both') doc_form_invalid = False if user_has_doc_perm and which_form in ['doc', 'both']: disclose_description = True post_data = request.POST.copy() post_data.update({'locale': request.locale}) doc_form = DocumentForm( post_data, instance=doc, can_create_tags=user.has_perm('taggit.add_tag')) doc_form.instance.locale = request.locale doc_form.instance.parent = parent_doc if which_form == 'both': rev_form = RevisionForm(request.POST) # If we are submitting the whole form, we need to check that # the Revision is valid before saving the Document. if doc_form.is_valid() and (which_form == 'doc' or rev_form.is_valid()): doc = doc_form.save(parent_doc) # Possibly schedule a rebuild. _maybe_schedule_rebuild(doc_form) if which_form == 'doc': url = urlparams(reverse('wiki.edit_document', args=[doc.slug]), opendescription=1) return HttpResponseRedirect(url) doc_slug = doc_form.cleaned_data['slug'] else: doc_form_invalid = True else: doc_slug = doc.slug if doc and user_has_rev_perm and which_form in ['rev', 'both']: rev_form = RevisionForm(request.POST) rev_form.instance.document = doc # for rev_form.clean() if rev_form.is_valid() and not doc_form_invalid: _save_rev_and_notify(rev_form, request.user, doc) url = reverse('wiki.document_revisions', args=[doc_slug]) return HttpResponseRedirect(url) show_revision_warning = _show_revision_warning(doc, base_rev) return jingo.render( request, 'wiki/translate.html', { 'parent': parent_doc, 'document': doc, 'document_form': doc_form, 'revision_form': rev_form, 'locale': request.locale, 'based_on': based_on_rev, 'disclose_description': disclose_description, 'show_revision_warning': show_revision_warning })
class UserProfile(SearchMixin, models.Model): # This field is required. user = models.OneToOneField(User) # Other fields here confirmation_code = models.CharField(max_length=32, editable=False, unique=True) is_confirmed = models.BooleanField(default=False) is_vouched = models.BooleanField(default=False) website = models.URLField(max_length=200, verbose_name=_lazy(u'Website'), blank=True, null=True) # Foreign Keys and Relationships vouched_by = models.ForeignKey('UserProfile', null=True) groups = models.ManyToManyField('groups.Group') bio = models.CharField(max_length=255, verbose_name=_lazy(u'Bio'), blank=True) photo = models.BooleanField(default=False) display_name = models.CharField(max_length=255) ircname = models.CharField(max_length=63, verbose_name=_lazy(u'IRC Nickname'), blank=True) objects = UserProfileManager() class Meta: db_table = 'profile' def is_complete(self): """ Tests if a user has all the information needed to move on past the original registration view """ return bool(self.user.last_name) def vouch(self, vouched_by, system=True, commit=True): changed = system # do we need to do a vouch? if system: self.is_vouched = True if vouched_by and vouched_by.is_vouched: changed = True self.is_vouched = True self.vouched_by = vouched_by if commit and changed: self.save() # Email the user and tell them they were vouched. self._email_now_vouched() def get_confirmation_url(self): url = (absolutify(reverse('confirm')) + '?code=' + self.confirmation_code) return url def get_send_confirmation_url(self): url = (reverse('send_confirmation') + '?' + urllib.urlencode({'user': self.user.username})) return url def get_photo_url(self, cachebust=False): """Gets a user's userpic URL. Appends cachebusting if requested.""" # Import this here to avoid circular imports because other apps are loading this file in init.py from phonebook.helpers import gravatar if self.photo: url = '%s/%d.jpg' % (settings.USERPICS_URL, self.user_id) if cachebust: url += '?%d' % int(time.time()) else: url = gravatar(self.user.email) return url def get_photo_file(self): return '%s/%d.jpg' % (settings.USERPICS_PATH, self.user_id) # TODO: get rid of this when larper is gone ETA Apr-2012 def get_unique_id(self): r = self.get_ldap_person() return r[1]['uniqueIdentifier'][0] def get_ldap_person(self): email = self.user.email or self.user.username return larper.get_user_by_email(email) def _email_now_vouched(self): """Email this user, letting them know they are now vouched.""" subject = _(u'You are now vouched on Mozillians!') message = _(u"You've now been vouched on Mozillians.org. " "You'll now be able to search, vouch " "and invite other Mozillians onto the site.") send_mail(subject, message, '*****@*****.**', [self.user.email]) @property def full_name(self): "%s %s" % (self.user.first_name, self.user.last_name) def __unicode__(self): """Return this user's name when their profile is called.""" return self.user.first_name def fields(self): attrs = ('id', 'is_confirmed', 'is_vouched', 'website', 'bio', 'photo', 'display_name', 'ircname') d = dict((a, getattr(self, a)) for a in attrs) # user data attrs = ('username', 'first_name', 'last_name', 'email', 'last_login', 'date_joined') d.update(dict((a, getattr(self.user, a)) for a in attrs)) # Index group ids... for fun. groups = list(self.groups.values_list('name', flat=True)) d.update(dict(groups=groups)) return d @classmethod def search(cls, query, vouched=None): """Sensible default search for UserProfiles.""" query = query.lower().strip() fields = ('first_name__text', 'last_name__text', 'display_name__text', 'username__text', 'bio__text', 'website__text', 'email__text', 'groups__text', 'first_name__startswith', 'last_name__startswith') q = dict((field, query) for field in fields) s = S(cls).query(or_=q) if vouched is not None: s = s.filter(is_vouched=vouched) return s
from tower import ugettext_lazy as _lazy from tower import ugettext as _ from django import forms from django.conf import settings from django.forms.widgets import CheckboxSelectMultiple from contentflagging.forms import ContentFlagForm import kuma.wiki.content from sumo.form_fields import StrippedCharField from .constants import (SLUG_CLEANSING_REGEX, REVIEW_FLAG_TAGS, LOCALIZATION_FLAG_TAGS, RESERVED_SLUGS) from .models import (Document, Revision, valid_slug_parent) TITLE_REQUIRED = _lazy(u'Please provide a title.') TITLE_SHORT = _lazy(u'The title is too short (%(show_value)s characters). ' u'It must be at least %(limit_value)s characters.') TITLE_LONG = _lazy(u'Please keep the length of the title to %(limit_value)s ' u'characters or less. It is currently %(show_value)s ' u'characters.') TITLE_PLACEHOLDER = _lazy(u'Name Your Article') SLUG_REQUIRED = _lazy(u'Please provide a slug.') SLUG_INVALID = _lazy(u'The slug provided is not valid.') SLUG_SHORT = _lazy(u'The slug is too short (%(show_value)s characters). ' u'It must be at least %(limit_value)s characters.') SLUG_LONG = _lazy(u'Please keep the length of the slug to %(limit_value)s ' u'characters or less. It is currently %(show_value)s ' u'characters.') SUMMARY_REQUIRED = _lazy(u'Please provide a summary.') SUMMARY_SHORT = _lazy(u'The summary is too short (%(show_value)s characters). '
def _review_actions(self): labels = {'prelim': _lazy('Grant preliminary review')} details = { 'prelim': _lazy('This will mark the files as ' 'preliminarily reviewed.'), 'info': _lazy('Use this form to request more information ' 'from the author. They will receive an email ' 'and be able to answer here. You will be ' 'notified by email when they reply.'), 'super': _lazy('If you have concerns about this add-on\'s ' 'security, copyright issues, or other ' 'concerns that an administrator should look ' 'into, enter your comments in the area ' 'below. They will be sent to ' 'administrators, not the author.'), 'reject': _lazy('This will reject the add-on and remove ' 'it from the review queue.'), 'comment': _lazy('Make a comment on this version. The ' 'author won\'t be able to see this.') } if self.addon.status == amo.STATUS_LITE: details['reject'] = _lazy('This will reject the files and remove ' 'them from the review queue.') if self.addon.status in (amo.STATUS_UNREVIEWED, amo.STATUS_NOMINATED): details['prelim'] = _lazy('This will mark the add-on as ' 'preliminarily reviewed. Future ' 'versions will undergo ' 'preliminary review.') elif self.addon.status == amo.STATUS_LITE: details['prelim'] = _lazy('This will mark the files as ' 'preliminarily reviewed. Future ' 'versions will undergo ' 'preliminary review.') elif self.addon.status == amo.STATUS_LITE_AND_NOMINATED: labels['prelim'] = _lazy('Retain preliminary review') details['prelim'] = _lazy('This will retain the add-on as ' 'preliminarily reviewed. Future ' 'versions will undergo preliminary ' 'review.') if self.review_type == 'pending': details['public'] = _lazy('This will approve a sandboxed version ' 'of a public add-on to appear on the ' 'public side.') details['reject'] = _lazy('This will reject a version of a public ' 'add-on and remove it from the queue.') else: details['public'] = _lazy('This will mark the add-on and its most ' 'recent version and files as public. ' 'Future versions will go into the ' 'sandbox until they are reviewed by an ' 'editor.') return labels, details
class RevisionForm(forms.ModelForm): """Form to create new revisions.""" title = StrippedCharField( min_length=1, max_length=255, required=False, widget=forms.TextInput(attrs={'placeholder': TITLE_PLACEHOLDER}), label=_lazy(u'Title:'), help_text=_lazy(u'Title of article'), error_messages={ 'required': TITLE_REQUIRED, 'min_length': TITLE_SHORT, 'max_length': TITLE_LONG }) slug = StrippedCharField(min_length=1, max_length=255, required=False, widget=forms.TextInput(), label=_lazy(u'Slug:'), help_text=_lazy(u'Article URL'), error_messages={ 'required': SLUG_REQUIRED, 'min_length': SLUG_SHORT, 'max_length': SLUG_LONG }) tags = StrippedCharField(required=False, label=_lazy(u'Tags:')) keywords = StrippedCharField(required=False, label=_lazy(u'Keywords:'), help_text=_lazy(u'Affects search results')) summary = StrippedCharField( required=False, min_length=5, max_length=1000, widget=forms.Textarea(), label=_lazy(u'Search result summary:'), help_text=_lazy(u'Only displayed on search results page'), error_messages={ 'required': SUMMARY_REQUIRED, 'min_length': SUMMARY_SHORT, 'max_length': SUMMARY_LONG }) content = StrippedCharField(min_length=5, max_length=300000, label=_lazy(u'Content:'), widget=forms.Textarea(), error_messages={ 'required': CONTENT_REQUIRED, 'min_length': CONTENT_SHORT, 'max_length': CONTENT_LONG }) comment = StrippedCharField(required=False, label=_lazy(u'Comment:')) review_tags = forms.MultipleChoiceField( label=_("Tag this revision for review?"), widget=CheckboxSelectMultiple, required=False, choices=REVIEW_FLAG_TAGS) localization_tags = forms.MultipleChoiceField( label=_("Tag this revision for localization?"), widget=CheckboxSelectMultiple, required=False, choices=LOCALIZATION_FLAG_TAGS) current_rev = forms.CharField(required=False, widget=forms.HiddenInput()) class Meta(object): model = Revision fields = ('title', 'slug', 'tags', 'keywords', 'summary', 'content', 'comment', 'based_on', 'toc_depth', 'render_max_age') def __init__(self, *args, **kwargs): # Snag some optional kwargs and delete them before calling # super-constructor. for n in ('section_id', 'is_iframe_target'): if n not in kwargs: setattr(self, n, None) else: setattr(self, n, kwargs[n]) del kwargs[n] super(RevisionForm, self).__init__(*args, **kwargs) self.fields['based_on'].widget = forms.HiddenInput() if self.instance and self.instance.pk: # Ensure both title and slug are populated from parent document, if # last revision didn't have them if not self.instance.title: self.initial['title'] = self.instance.document.title if not self.instance.slug: self.initial['slug'] = self.instance.document.slug content = self.instance.content if not self.instance.document.is_template: tool = kuma.wiki.content.parse(content) tool.injectSectionIDs() if self.section_id: tool.extractSection(self.section_id) tool.filterEditorSafety() content = tool.serialize() self.initial['content'] = content self.initial['review_tags'] = [ x.name for x in self.instance.review_tags.all() ] self.initial['localization_tags'] = [ x.name for x in self.instance.localization_tags.all() ] if self.section_id: self.fields['toc_depth'].required = False def _clean_collidable(self, name): value = self.cleaned_data[name] if self.is_iframe_target: # Since these collidables can change the URL of the page, changes # to them are ignored for an iframe submission return getattr(self.instance.document, name) error_message = {'slug': SLUG_COLLIDES}.get(name, OTHER_COLLIDES) try: existing_doc = Document.objects.get( locale=self.instance.document.locale, **{name: value}) if self.instance and self.instance.document: if (not existing_doc.redirect_url() and existing_doc.pk != self.instance.document.pk): # There's another document with this value, # and we're not a revision of it. raise forms.ValidationError(error_message) else: # This document-and-revision doesn't exist yet, so there # shouldn't be any collisions at all. raise forms.ValidationError(error_message) except Document.DoesNotExist: # No existing document for this value, so we're good here. pass return value def clean_slug(self): # TODO: move this check somewhere else? # edits can come in without a slug, so default to the current doc slug if not self.cleaned_data['slug']: existing_slug = self.instance.document.slug self.cleaned_data['slug'] = self.instance.slug = existing_slug cleaned_slug = self._clean_collidable('slug') return cleaned_slug def clean_content(self): """Validate the content, performing any section editing if necessary""" content = self.cleaned_data['content'] # If we're editing a section, we need to replace the section content # from the current revision. if self.section_id and self.instance and self.instance.document: # Make sure we start with content form the latest revision. full_content = self.instance.document.current_revision.content # Replace the section content with the form content. tool = kuma.wiki.content.parse(full_content) tool.replaceSection(self.section_id, content) content = tool.serialize() return content def clean_current_rev(self): """If a current revision is supplied in the form, compare it against what the document claims is the current revision. If there's a difference, then an edit has occurred since the form was constructed and we treat it as a mid-air collision.""" current_rev = self.cleaned_data.get('current_rev', None) if not current_rev: # If there's no current_rev, just bail. return current_rev try: doc_current_rev = self.instance.document.current_revision.id if unicode(current_rev) != unicode(doc_current_rev): if (self.section_id and self.instance and self.instance.document): # This is a section edit. So, even though the revision has # changed, it still might not be a collision if the section # in particular hasn't changed. orig_ct = (Revision.objects.get( pk=current_rev).get_section_content(self.section_id)) curr_ct = (self.instance.document.current_revision. get_section_content(self.section_id)) if orig_ct != curr_ct: # Oops. Looks like the section did actually get # changed, so yeah this is a collision. raise forms.ValidationError(MIDAIR_COLLISION) return current_rev else: # No section edit, so this is a flat-out collision. raise forms.ValidationError(MIDAIR_COLLISION) except Document.DoesNotExist: # If there's no document yet, just bail. return current_rev def save_section(self, creator, document, **kwargs): """Save a section edit.""" # This is separate because the logic is slightly different and # may need to evolve over time; a section edit doesn't submit # all the fields, and we need to account for that when we # construct the new Revision. old_rev = Document.objects.get( pk=self.instance.document.id).current_revision new_rev = super(RevisionForm, self).save(commit=False, **kwargs) new_rev.document = document new_rev.creator = creator new_rev.toc_depth = old_rev.toc_depth new_rev.save() new_rev.review_tags.set(*[t.name for t in old_rev.review_tags.all()]) return new_rev def save(self, creator, document, **kwargs): """Persist me, and return the saved Revision. Take several other necessary pieces of data that aren't from the form. """ if self.section_id and self.instance and \ self.instance.document: return self.save_section(creator, document, **kwargs) # Throws a TypeError if somebody passes in a commit kwarg: new_rev = super(RevisionForm, self).save(commit=False, **kwargs) new_rev.document = document new_rev.creator = creator new_rev.toc_depth = self.cleaned_data['toc_depth'] new_rev.save() new_rev.review_tags.set(*self.cleaned_data['review_tags']) new_rev.localization_tags.set(*self.cleaned_data['localization_tags']) return new_rev
class PackagerFeaturesForm(forms.Form): about_dialog = forms.BooleanField( required=False, label=_lazy(u'About dialog'), help_text=_lazy(u'Creates a standard About dialog for your ' 'extension')) preferences_dialog = forms.BooleanField( required=False, label=_lazy(u'Preferences dialog'), help_text=_lazy(u'Creates an example Preferences window')) toolbar = forms.BooleanField( required=False, label=_lazy(u'Toolbar'), help_text=_lazy(u'Creates an example toolbar for your extension')) toolbar_button = forms.BooleanField( required=False, label=_lazy(u'Toolbar button'), help_text=_lazy(u'Creates an example button on the browser ' 'toolbar')) main_menu_command = forms.BooleanField( required=False, label=_lazy(u'Main menu command'), help_text=_lazy(u'Creates an item on the Tools menu')) context_menu_command = forms.BooleanField( required=False, label=_lazy(u'Context menu command'), help_text=_lazy(u'Creates a context menu item for images')) sidebar_support = forms.BooleanField( required=False, label=_lazy(u'Sidebar support'), help_text=_lazy(u'Creates an example sidebar panel'))
def __unicode__(self): message = _lazy(u'%s banned by %s') % (self.user, self.by) if not self.is_active: message = _lazy(u"%s (no longer active)") % message return message
class ProfileForm(happyforms.ModelForm): photo = forms.ImageField(label=_lazy(u'Profile Photo'), required=False) photo_delete = forms.BooleanField(label=_lazy(u'Remove Profile Photo'), required=False) date_mozillian = forms.DateField(required=False, widget=MonthYearWidget(years=range( 1998, datetime.today().year + 1), required=False)) groups = forms.CharField(label=_lazy( u'Start typing to add a group (example: Marketing, ' 'Support, WebDev, Thunderbird)'), required=False) languages = forms.CharField(label=_lazy( u'Start typing to add a language you speak (example: ' 'English, French, German)'), required=False) skills = forms.CharField(label=_lazy( u'Start typing to add a skill (example: Python, ' 'javascript, Graphic Design, User Research)'), required=False) timezone = forms.ChoiceField(required=False, choices=zip(common_timezones, common_timezones)) class Meta: model = UserProfile fields = ('full_name', 'ircname', 'website', 'bio', 'photo', 'country', 'region', 'city', 'allows_community_sites', 'allows_mozilla_sites', 'date_mozillian', 'timezone', 'privacy_photo', 'privacy_full_name', 'privacy_ircname', 'privacy_email', 'privacy_timezone', 'privacy_website', 'privacy_bio', 'privacy_city', 'privacy_region', 'privacy_country', 'privacy_groups', 'privacy_skills', 'privacy_languages', 'privacy_date_mozillian') widgets = {'bio': forms.Textarea()} def __init__(self, *args, **kwargs): locale = kwargs.pop('locale', 'en-US') super(ProfileForm, self).__init__(*args, **kwargs) country_list = product_details.get_regions(locale).items() country_list = sorted(country_list, key=lambda country: country[1]) country_list.insert(0, ('', '----')) self.fields['country'].choices = country_list def clean_groups(self): """Groups are saved in lowercase because it's easy and consistent. """ if not re.match(r'^[a-zA-Z0-9 .:,-]*$', self.cleaned_data['groups']): raise forms.ValidationError( _(u'Groups can only contain ' 'alphanumeric characters, dashes, ' 'spaces.')) system_groups = [ g.name for g in self.instance.groups.all() if g.system ] groups = self.cleaned_data['groups'] new_groups = filter( lambda x: x, map(lambda x: x.strip() or False, groups.lower().split(','))) return system_groups + new_groups def clean_languages(self): if not re.match(r'^[a-zA-Z0-9 .:,-]*$', self.cleaned_data['languages']): raise forms.ValidationError( _(u'Languages can only contain ' 'alphanumeric characters, dashes, ' 'spaces.')) languages = self.cleaned_data['languages'] return filter( lambda x: x, map(lambda x: x.strip() or False, languages.lower().split(','))) def clean_skills(self): if not re.match(r'^[a-zA-Z0-9 .:,-]*$', self.cleaned_data['skills']): raise forms.ValidationError( _(u'Skills can only contain ' 'alphanumeric characters, dashes, ' 'spaces.')) skills = self.cleaned_data['skills'] return filter( lambda x: x, map(lambda x: x.strip() or False, skills.lower().split(','))) def save(self): """Save the data to profile.""" self.instance.set_membership(Group, self.cleaned_data['groups']) self.instance.set_membership(Skill, self.cleaned_data['skills']) self.instance.set_membership(Language, self.cleaned_data['languages']) super(ProfileForm, self).save()
class PackagerCompatForm(forms.Form): enabled = forms.BooleanField(required=False) min_ver = forms.ModelChoiceField(AppVersion.objects.none(), empty_label=None, required=False, label=_lazy(u'Minimum')) max_ver = forms.ModelChoiceField(AppVersion.objects.none(), empty_label=None, required=False, label=_lazy(u'Maximum')) def __init__(self, *args, **kwargs): super(PackagerCompatForm, self).__init__(*args, **kwargs) if not self.initial: return self.app = self.initial['application'] qs = (AppVersion.objects.filter( application=self.app.id).order_by('-version_int')) self.fields['enabled'].label = self.app.pretty if self.app == amo.FIREFOX: self.fields['enabled'].widget.attrs['checked'] = True # Don't allow version ranges as the minimum version. self.fields['min_ver'].queryset = qs.filter(~Q(version__contains='*')) self.fields['max_ver'].queryset = qs.all() # Unreasonably hardcode a reasonable default minVersion. if self.app in (amo.FIREFOX, amo.MOBILE, amo.THUNDERBIRD): try: self.fields['min_ver'].initial = qs.filter( version=amo.DEFAULT_MINVER)[0] except (IndexError, AttributeError): pass def clean_min_ver(self): if self.cleaned_data['enabled'] and not self.cleaned_data['min_ver']: raise_required() return self.cleaned_data['min_ver'] def clean_max_ver(self): if self.cleaned_data['enabled'] and not self.cleaned_data['max_ver']: raise_required() return self.cleaned_data['max_ver'] def clean(self): if self.errors: return data = self.cleaned_data if data['enabled']: min_ver = data['min_ver'] max_ver = data['max_ver'] if not (min_ver and max_ver): raise forms.ValidationError(_('Invalid version range.')) if min_ver.version_int > max_ver.version_int: raise forms.ValidationError( _('Min version must be less than Max version.')) # Pass back the app name and GUID. data['min_ver'] = str(min_ver) data['max_ver'] = str(max_ver) data['name'] = self.app.pretty data['guid'] = self.app.guid return data
class EditorQueueTable(SQLTable, ItemStateTable): addon_name = tables.Column(verbose_name=_lazy(u'Addon')) addon_type_id = tables.Column(verbose_name=_lazy(u'Type')) waiting_time_min = tables.Column(verbose_name=_lazy(u'Waiting Time')) flags = tables.Column(verbose_name=_lazy(u'Flags'), sortable=False) applications = tables.Column(verbose_name=_lazy(u'Applications'), sortable=False) platforms = tables.Column(verbose_name=_lazy(u'Platforms'), sortable=False) additional_info = tables.Column(verbose_name=_lazy(u'Additional'), sortable=False) def render_addon_name(self, row): url = reverse('editors.review', args=[row.addon_slug]) self.increment_item() return u'<a href="%s">%s <em>%s</em></a>' % ( url, jinja2.escape( row.addon_name), jinja2.escape(row.latest_version)) def render_addon_type_id(self, row): return amo.ADDON_TYPE[row.addon_type_id] def render_additional_info(self, row): info = [] if row.is_site_specific: info.append(_lazy(u'Site Specific')) if row.external_software: info.append(_lazy(u'Requires External Software')) if row.binary or row.binary_components: info.append(_lazy(u'Binary Components')) return u', '.join([jinja2.escape(i) for i in info]) def render_applications(self, row): # TODO(Kumar) show supported version ranges on hover (if still needed) icon = u'<div class="app-icon ed-sprite-%s" title="%s"></div>' return u''.join([ icon % (amo.APPS_ALL[i].short, amo.APPS_ALL[i].pretty) for i in row.application_ids ]) def render_platforms(self, row): icons = [] html = u'<div class="platform-icon plat-sprite-%s" title="%s"></div>' for platform in row.file_platform_ids: icons.append(html % (amo.PLATFORMS[int(platform)].shortname, amo.PLATFORMS[int(platform)].name)) return u''.join(icons) def render_flags(self, row): return ''.join(u'<div class="app-icon ed-sprite-%s" ' u'title="%s"></div>' % flag for flag in row.flags) def render_waiting_time_min(self, row): if row.waiting_time_min == 0: r = _lazy('moments ago') elif row.waiting_time_hours == 0: # L10n: first argument is number of minutes r = ngettext(u'{0} minute', u'{0} minutes', row.waiting_time_min).format(row.waiting_time_min) elif row.waiting_time_days == 0: # L10n: first argument is number of hours r = ngettext(u'{0} hour', u'{0} hours', row.waiting_time_hours).format(row.waiting_time_hours) else: # L10n: first argument is number of days r = ngettext(u'{0} day', u'{0} days', row.waiting_time_days).format(row.waiting_time_days) return jinja2.escape(r) @classmethod def translate_sort_cols(cls, colname): legacy_sorts = { 'name': 'addon_name', 'age': 'waiting_time_min', 'type': 'addon_type_id', } return legacy_sorts.get(colname, colname) @classmethod def default_order_by(cls): return '-waiting_time_min' @classmethod def review_url(cls, row): return reverse('editors.review', args=[row.addon_slug]) class Meta: sortable = True columns = [ 'addon_name', 'addon_type_id', 'waiting_time_min', 'flags', 'applications', 'additional_info' ]
class PackagerBasicForm(forms.Form): name = forms.CharField( min_length=5, max_length=50, help_text=_lazy(u'Give your add-on a name. The most successful ' 'add-ons give some indication of their function in ' 'their name.')) description = forms.CharField( required=False, widget=forms.Textarea, help_text=_lazy(u'Briefly describe your add-on in one sentence. ' 'This appears in the Add-ons Manager.')) version = forms.CharField( max_length=32, help_text=_lazy(u'Enter your initial version number. Depending on the ' 'number of releases and your preferences, this is ' 'usually 0.1 or 1.0')) id = forms.CharField( help_text=_lazy(u'Each add-on requires a unique ID in the form of a ' 'UUID or an email address, such as ' '[email protected]. The email address does not ' 'have to be valid.')) package_name = forms.CharField( min_length=5, max_length=50, help_text=_lazy(u'The package name of your add-on used within the ' 'browser. This should be a short form of its name ' '(for example, Test Extension might be ' 'test_extension).')) author_name = forms.CharField( help_text=_lazy(u'Enter the name of the person or entity to be ' 'listed as the author of this add-on.')) contributors = forms.CharField( required=False, widget=forms.Textarea, help_text=_lazy(u'Enter the names of any other contributors to this ' 'extension, one per line.')) def clean_name(self): name = self.cleaned_data['name'] addons.forms.clean_name(name) name_regex = re.compile('(mozilla|firefox|thunderbird)', re.I) if name_regex.match(name): raise forms.ValidationError( _('Add-on names should not contain Mozilla trademarks.')) return name def clean_package_name(self): slug = self.cleaned_data['package_name'] if slugify(slug, ok='_', lower=False, delimiter='_') != slug: raise forms.ValidationError( _('Enter a valid package name consisting of letters, numbers, ' 'or underscores.')) if Addon.objects.filter(slug=slug).exists(): raise forms.ValidationError( _('This package name is already in use.')) if BlacklistedSlug.blocked(slug): raise forms.ValidationError( _(u'The package name cannot be: %s.' % slug)) return slug def clean_id(self): id_regex = re.compile( """(\{[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}\} | # GUID [a-z0-9-\.\+_]*\@[a-z0-9-\._]+) # Email format""", re.I | re.X) if not id_regex.match(self.cleaned_data['id']): raise forms.ValidationError( _('The add-on ID must be a UUID string or an email ' 'address.')) return self.cleaned_data['id'] def clean_version(self): if not VERSION_RE.match(self.cleaned_data['version']): raise forms.ValidationError(_('The version string is invalid.')) return self.cleaned_data['version']
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 Profile(ModelBase): """Profile model for django users, get it with user.get_profile().""" user = models.OneToOneField(User, primary_key=True, verbose_name=_lazy(u'User')) name = models.CharField(max_length=255, null=True, blank=True, verbose_name=_lazy(u'Display name')) public_email = models.BooleanField( # show/hide email default=False, verbose_name=_lazy(u'Make my email public')) avatar = models.ImageField(upload_to=settings.USER_AVATAR_PATH, null=True, blank=True, verbose_name=_lazy(u'Avatar'), max_length=settings.MAX_FILEPATH_LENGTH) bio = models.TextField(null=True, blank=True, verbose_name=_lazy(u'Biography')) website = models.URLField(max_length=255, null=True, blank=True, verbose_name=_lazy(u'Website')) twitter = models.URLField(max_length=255, null=True, blank=True, verbose_name=_lazy(u'Twitter URL')) facebook = models.URLField(max_length=255, null=True, blank=True, verbose_name=_lazy(u'Facebook URL')) irc_handle = models.CharField(max_length=255, null=True, blank=True, verbose_name=_lazy(u'IRC nickname')) timezone = TimeZoneField(null=True, blank=True, verbose_name=_lazy(u'Timezone')) country = models.CharField(max_length=2, choices=COUNTRIES, null=True, blank=True, verbose_name=_lazy(u'Country')) # No city validation city = models.CharField(max_length=255, null=True, blank=True, verbose_name=_lazy(u'City')) livechat_id = models.CharField(default=None, null=True, blank=True, max_length=255, verbose_name=_lazy(u'Livechat ID')) def __unicode__(self): return unicode(self.user)