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 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_body(self, content_type): """Try rendering the body template and capture any errors.""" assert content_type in EmailTemplate.CONTENT_TYPES, _lazy("Invalid content type.") if content_type == EmailTemplate.CONTENT_TYPE_PLAIN: field_name = 'body_text' if content_type == EmailTemplate.CONTENT_TYPE_HTML: field_name = 'body_html' try: self.render_body({}, content_type=content_type) except TemplateDoesNotExist as ex: return {field_name: _lazy("Template does not exist: {}".format(ex))} except TemplateSyntaxError as ex: return {field_name: str(ex)} else: return {}
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 _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 _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 if _is_youtube_url(title): parsed_url = urlparse(title) netloc = parsed_url.netloc 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)
def locale_name(locale, native=False, default=_lazy(u'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 editor_page_title(context, title=None, addon=None): """Wrapper for editor page titles. Eerily similar to dev_page_title.""" if addon: title = u'%s :: %s' % (title, addon.name) else: section = _lazy('Editor Tools') title = u'%s :: %s' % (title, section) if title else section return page_title(context, title)
def render_body(self, context, content_type=CONTENT_TYPE_PLAIN, processors=CONTEXT_PROCESSORS): """Render email body in plain text or HTML format.""" assert content_type in EmailTemplate.CONTENT_TYPES, _lazy("Invalid content type.") if content_type == EmailTemplate.CONTENT_TYPE_PLAIN: ctx = Context(helpers.patch_context(context, processors), autoescape=False) return Template(self.body_text).render(ctx) if content_type == EmailTemplate.CONTENT_TYPE_HTML: ctx = Context(helpers.patch_context(context, processors)) return Template(self.body_html).render(ctx)
def _hook_button(self, parser, space, btn_type): btn_type, params = build_hook_params(btn_type, self.locale) if btn_type == 'refresh': template = 'wikiparser/hook_refresh_button.html' else: return _lazy(u'Button of type "%s" does not exist.') % btn_type return jingo.get_env().get_template(template).render({'params': params})
def check_file_size(f, max_allowed_size): """Check the file size of f is less than max_allowed_size Raise FileTooLargeError if the check fails. """ if f.size > max_allowed_size: message = _lazy(u'"%s" is too large (%sKB), the limit is %sKB') % ( f.name, f.size >> 10, max_allowed_size >> 10) raise FileTooLargeError(message)
def _request_args(): from django.conf import settings from django.utils.translation import ugettext_lazy as _lazy args = {'siteName': _lazy('Mozilla Reps'), } if settings.SITE_URL.startswith('https'): args['siteLogo'] = '/static/base/img/remo/remo_logo_medium.png' return args
def _validate_subject(self): """Try rendering the body template and capture any errors.""" try: self.render_subject({}) except TemplateDoesNotExist as ex: return {'subject': _lazy("Template does not exist: {}".format(ex))} except TemplateSyntaxError as ex: return {'subject': str(ex)} else: return {}
def __init__(self, user, *args, **kwargs): super(BaseZendeskForm, self).__init__(*args, **kwargs) self.user = user # Add email field for users not logged in. if not user.is_authenticated(): email = forms.EmailField( label=_lazy(u"Email:"), widget=forms.TextInput(attrs={"placeholder": EMAIL_PLACEHOLDER}) ) self.fields["email"] = email
def _browserid_request_args(): from django.conf import settings from django.utils.translation import ugettext_lazy as _lazy args = { 'siteName': _lazy('Mozillians'), } if settings.SITE_URL.startswith('https'): args['siteLogo'] = urljoin(STATIC_URL, "mozillians/img/apple-touch-icon-144.png") return args
def extra_context(cls, book, request): from django.utils.translation import ugettext_lazy as _lazy all_statuses = (BookStatus.objects .filter(book=book) .annotate(num_chapters=Count('chapter')) .order_by('-weight')) all_statuses = [{'pk': status.pk, 'name': _lazy(status.name)} for status in all_statuses] return { 'roles_permissions': security.get_user_permissions(request.user, book), 'status_list': all_statuses, }
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): actions = SortedDict() if request is None: # If request is not set, it means we are just (ab)using the # ReviewHelper for its `handler` attribute and we don't care about # the actions. return actions labels, details = self._review_actions() reviewable_because_complete = addon.status not in ( amo.STATUS_NULL, amo.STATUS_DELETED) reviewable_because_admin = ( not addon.admin_review or acl.action_allowed(request, 'ReviewerAdminTools', 'View')) reviewable_because_submission_time = ( not is_limited_reviewer(request) or (addon.latest_version is not None and addon.latest_version.nomination is not None and (datetime.datetime.now() - addon.latest_version.nomination >= datetime.timedelta(hours=REVIEW_LIMITED_DELAY_HOURS)))) reviewable_because_pending = addon.latest_version is not None and ( len(addon.latest_version.is_unreviewed) > 0 or addon.status == amo.STATUS_LITE_AND_NOMINATED) if (reviewable_because_complete and reviewable_because_admin and reviewable_because_submission_time and reviewable_because_pending): if self.review_type != 'preliminary': if addon.is_listed: label = _lazy('Push to public') else: label = _lazy('Grant full review') actions['public'] = {'method': self.handler.process_public, 'minimal': False, 'label': label} # An unlisted sideload add-on, which requests a full review, cannot # be granted a preliminary review. prelim_allowed = not waffle.flag_is_active( request, 'no-prelim-review') and addon.is_listed if prelim_allowed or self.review_type == 'preliminary': 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 _request_args(): from django.contrib.staticfiles import finders from django.utils.translation import ugettext_lazy as _lazy site_logo = open(finders.find('img/qa-logo.png'), 'rb').read().encode('base64') logo_uri = urllib.quote('data:image/png;base64,{image}'.format(image=site_logo), safe=',:;/') return { 'privacyPolicy': 'https://www.mozilla.org/privacy/websites/', 'siteName': _lazy(u'One and Done'), 'termsOfService': 'https://www.mozilla.org/about/legal/', 'siteLogo': mark_safe(logo_uri), 'backgroundColor': '#E0DDD4', }
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")), ("sources_provided", "sources-provided", _lazy("Sources provided")), ("is_webextension", "webextension", _lazy("WebExtension")), ) return [(cls, title) for (prop, cls, title) in props if getattr(self, prop)]
def __init__(self, *args, **kw): super(ReviewLogForm, self).__init__(*args, **kw) # L10n: start, as in "start date" self.fields['start'].widget.attrs = {'placeholder': _('start'), 'size': 10} # L10n: end, as in "end date" self.fields['end'].widget.attrs = {'size': 10, 'placeholder': _('end')} self.fields['search'].widget.attrs = { # L10n: Descript of what can be searched for. 'placeholder': _lazy(u'app, reviewer, or comment'), 'size': 30}
def __init__(self, *args, **kw): super(ReviewLogForm, self).__init__(*args, **kw) # L10n: start, as in "start date" self.fields["start"].widget.attrs = {"placeholder": _("start"), "size": 10} # L10n: end, as in "end date" self.fields["end"].widget.attrs = {"size": 10, "placeholder": _("end")} self.fields["search"].widget.attrs = { # L10n: Descript of what can be searched for. "placeholder": _lazy(u"app, reviewer, or comment"), "size": 30, }
def _hook_image_tag(self, parser, space, name): """Adds syntax for inserting images.""" title, params = build_hook_params(name, self.locale, IMAGE_PARAMS, IMAGE_PARAM_VALUES) message = _lazy(u'The image "%s" does not exist.') % title image = get_object_fallback(Image, title, self.locale, message) if isinstance(image, basestring): return image template = jingo.get_env().get_template(self.image_template) r_kwargs = {'image': image, 'params': params, 'STATIC_URL': settings.STATIC_URL} return template.render(r_kwargs)
def process_request(self, request): try: auth = request.GET.get('auth') except IOError: # Django can throw an IOError when trying to read the GET # data. return if auth is None or (request.user and request.user.is_authenticated()): return user = authenticate(auth=auth) if user and user.is_active: login(request, user) msg = _lazy(u'You have been automatically logged in.') messages.success(request, msg)
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)
def notify_email(self, template, subject): """Notify the authors that their addon has been reviewed.""" emails = [a.email for a in self.addon.authors.all()] data = self.data.copy() if self.data else {} data.update(self.get_context_data()) data['tested'] = '' os, app = data.get('operating_systems'), data.get('applications') if os and app: data['tested'] = 'Tested on %s with %s' % (os, app) elif os and not app: data['tested'] = 'Tested on %s' % os elif not os and app: data['tested'] = 'Tested with %s' % app data['addon_type'] = (_lazy('add-on')) send_mail('editors/emails/%s.ltxt' % template, subject % (self.addon.name, self.version.version), emails, Context(data), perm_setting='editor_reviewed')
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')), ('sources_provided', 'sources-provided', _lazy('Sources provided')), ('is_webextension', 'webextension', _lazy('WebExtension')), ) return [(cls, title) for (prop, cls, title) in props if getattr(self, prop)]
def _mails(self, users_and_watches): post_url = add_utm(self.reply.get_absolute_url(), 'forums-post') c = {'post': self.reply.content, 'post_html': self.reply.content_parsed, 'author': self.reply.author, 'host': Site.objects.get_current().domain, 'thread': self.reply.thread.title, 'forum': self.reply.thread.forum.name, 'post_url': post_url} return emails_with_users_and_watches( subject=_lazy(u'Re: {forum} - {thread}'), text_template='forums/email/new_post.ltxt', html_template='forums/email/new_post.html', context_vars=c, users_and_watches=users_and_watches)
def get_flags(record): """Return a list of tuples (indicating which flags should be displayed for a particular add-on.""" props = ( ('admin_review', 'admin-review', _lazy('Admin Review')), ('is_jetpack', 'jetpack', _lazy('Jetpack Add-on')), ('requires_restart', 'requires_restart', _lazy('Requires Restart')), ('has_info_request', 'info', _lazy('More Information Requested')), ('has_editor_comment', 'editor', _lazy('Contains Editor Comment')), ('sources_provided', 'sources-provided', _lazy('Sources provided')), ('is_webextension', 'webextension', _lazy('WebExtension')), ) return [(cls, title) for (prop, cls, title) in props if getattr(record, prop)]
class AdvancedSearchForm(forms.Form): """Django form for handling display and validation""" # Common fields q = forms.CharField(required=False, max_length=MAX_QUERY_LENGTH) w = forms.TypedChoiceField( required=False, coerce=int, widget=forms.HiddenInput, empty_value=constants.WHERE_BASIC, choices=( (constants.WHERE_SUPPORT, None), (constants.WHERE_WIKI, None), (constants.WHERE_BASIC, None), (constants.WHERE_DISCUSSION, None), ), ) # TODO: get rid of this. a = forms.IntegerField(required=False, widget=forms.HiddenInput) # KB fields topics = forms.MultipleChoiceField(required=False, widget=forms.CheckboxSelectMultiple(), label=_lazy("Topics")) language = forms.ChoiceField(required=False, label=_lazy("Language"), choices=SEARCH_LANGUAGES) category = TypedMultipleChoiceField( required=False, coerce=int, widget=forms.CheckboxSelectMultiple, label=_lazy("Category"), choices=CATEGORIES, coerce_only=True, ) product = forms.MultipleChoiceField(required=False, label=_lazy("Relevant to"), widget=forms.CheckboxSelectMultiple()) include_archived = forms.BooleanField( required=False, label=_lazy("Include obsolete articles?")) sortby_documents = forms.TypedChoiceField( required=False, empty_value=constants.SORTBY_DOCUMENTS_CHOICES[0][0], label=_lazy("Sort results by"), choices=constants.SORTBY_DOCUMENTS_CHOICES, ) # Support questions and discussion forums fields created = forms.TypedChoiceField( required=False, coerce=int, empty_value=0, label=_lazy("Created"), choices=constants.DATE_LIST, ) created_date = forms.CharField(required=False) updated = forms.TypedChoiceField( required=False, coerce=int, empty_value=0, label=_lazy("Last updated"), choices=constants.DATE_LIST, ) updated_date = forms.CharField(required=False) user_widget = forms.TextInput(attrs={ "placeholder": _lazy("username"), "class": "auto-fill" }) # Discussion forums fields author = forms.CharField(required=False, widget=user_widget) sortby = forms.TypedChoiceField( required=False, coerce=int, empty_value=0, label=_lazy("Sort results by"), choices=constants.SORTBY_FORUMS, ) thread_type = TypedMultipleChoiceField( required=False, coerce=int, widget=forms.CheckboxSelectMultiple, label=_lazy("Thread type"), choices=constants.DISCUSSION_STATUS_LIST, coerce_only=True, ) forum = TypedMultipleChoiceField( required=False, coerce=int, label=_lazy("Search in forum"), choices=[], # Note: set choices with set_allowed_forums coerce_only=True, ) # Support questions fields asked_by = forms.CharField(required=False, widget=user_widget) answered_by = forms.CharField(required=False, widget=user_widget) sortby_questions = forms.TypedChoiceField( required=False, coerce=int, empty_value=0, label=_lazy("Sort results by"), choices=constants.SORTBY_QUESTIONS, ) is_locked = forms.TypedChoiceField( required=False, coerce=int, empty_value=0, widget=forms.RadioSelect, label=_lazy("Locked"), choices=constants.TERNARY_LIST, ) is_archived = forms.TypedChoiceField( required=False, coerce=int, empty_value=0, widget=forms.RadioSelect, label=_lazy("Archived"), choices=constants.TERNARY_LIST, ) is_solved = forms.TypedChoiceField( required=False, coerce=int, empty_value=0, widget=forms.RadioSelect, label=_lazy("Solved"), choices=constants.TERNARY_LIST, ) has_answers = forms.TypedChoiceField( required=False, coerce=int, empty_value=0, widget=forms.RadioSelect, label=_lazy("Has answers"), choices=constants.TERNARY_LIST, ) has_helpful = forms.TypedChoiceField( required=False, coerce=int, empty_value=0, widget=forms.RadioSelect, label=_lazy("Has helpful answers"), choices=constants.TERNARY_LIST, ) num_voted = forms.TypedChoiceField( required=False, coerce=int, empty_value=0, label=_lazy("Votes"), choices=constants.NUMBER_LIST, ) num_votes = forms.IntegerField(required=False) tag_widget = forms.TextInput(attrs={ "placeholder": _lazy("tag1, tag2"), "class": "auto-fill" }) q_tags = forms.CharField(label=_lazy("Tags"), required=False, widget=tag_widget) def __init__(self, *args, **kwargs): super(AdvancedSearchForm, self).__init__(*args, **kwargs) product_field = self.fields["product"] product_field.choices = Product.objects.values_list("slug", "title") topics_field = self.fields["topics"] topics_field.choices = Topic.objects.values_list("slug", "title").distinct() def clean(self): """Clean up data and set defaults""" c = self.cleaned_data # Validate created and updated dates date_fields = (("created", "created_date"), ("updated", "updated_date")) for field_option, field_date in date_fields: if c[field_date] != "": try: created_timestamp = time.mktime( time.strptime(c[field_date], "%m/%d/%Y")) c[field_date] = int(created_timestamp) except (ValueError, OverflowError): c[field_option] = None else: c[field_option] = None # Empty value defaults to int c["num_votes"] = c.get("num_votes") or 0 return c def clean_category(self): category = self.cleaned_data["category"] if not category: category = settings.SEARCH_DEFAULT_CATEGORIES return category def set_allowed_forums(self, user): """Sets the 'forum' field choices to forums the user can see.""" forums = [(f.id, f.name) for f in DiscussionForum.authorized_forums_for_user(user)] self.fields["forum"].choices = forums
class AppFormBasic(AddonFormBase): """Form to edit basic app info.""" slug = forms.CharField(max_length=30, widget=forms.TextInput) manifest_url = forms.URLField() description = TransField( required=True, label=_lazy(u'Provide a detailed description of your app'), help_text=_lazy(u'This description will appear on the details page.'), widget=TransTextarea) tags = forms.CharField( label=_lazy(u'Search Keywords:'), required=False, widget=forms.Textarea(attrs={'rows': 3}), help_text=_lazy( u'The search keywords are used to return search results in the ' u'Firefox Marketplace. Be sure to include a keywords that ' u'accurately reflect your app.')) class Meta: model = Webapp fields = ('slug', 'manifest_url', 'description', 'tags') def __init__(self, *args, **kw): # Force the form to use app_slug. We want to keep # this under "slug" so all the js continues to work. kw.setdefault('initial', {})['slug'] = kw['instance'].app_slug super(AppFormBasic, self).__init__(*args, **kw) self.old_manifest_url = self.instance.manifest_url if self.instance.is_packaged: # Manifest URL cannot be changed for packaged apps. del self.fields['manifest_url'] self.initial['tags'] = ', '.join(self.get_tags(self.instance)) def clean_tags(self): return clean_tags(self.request, self.cleaned_data['tags']) def get_tags(self, addon): if can_edit_restricted_tags(self.request): return list(addon.tags.values_list('tag_text', flat=True)) else: return list( addon.tags.filter(restricted=False).values_list('tag_text', flat=True)) def _post_clean(self): # Switch slug to app_slug in cleaned_data and self._meta.fields so # we can update the app_slug field for webapps. try: self._meta.fields = list(self._meta.fields) slug_idx = self._meta.fields.index('slug') data = self.cleaned_data if 'slug' in data: data['app_slug'] = data.pop('slug') self._meta.fields[slug_idx] = 'app_slug' self.fields['app_slug'] = self.fields['slug'] super(AppFormBasic, self)._post_clean() finally: self._meta.fields[slug_idx] = 'slug' del self.fields['app_slug'] def clean_slug(self): slug = self.cleaned_data['slug'] slug_validator(slug, lower=False) if slug != self.instance.app_slug: if Webapp.objects.filter(app_slug=slug).exists(): raise forms.ValidationError( _('This slug is already in use. Please choose another.')) if BlockedSlug.blocked(slug): raise forms.ValidationError( _('The slug cannot be "%s". ' 'Please choose another.' % slug)) return slug.lower() def clean_manifest_url(self): manifest_url = self.cleaned_data['manifest_url'] # Only verify if manifest changed. if 'manifest_url' in self.changed_data: verify_app_domain(manifest_url, exclude=self.instance) return manifest_url def save(self, addon, commit=False): # We ignore `commit`, since we need it to be `False` so we can save # the ManyToMany fields on our own. addonform = super(AppFormBasic, self).save(commit=False) addonform.save() if 'manifest_url' in self.changed_data: before_url = self.old_manifest_url after_url = self.cleaned_data['manifest_url'] # If a non-admin edited the manifest URL, add to Re-review Queue. if not acl.action_allowed(self.request, 'Admin', '%'): log.info(u'[Webapp:%s] (Re-review) Manifest URL changed ' u'from %s to %s' % (self.instance, before_url, after_url)) msg = (_(u'Manifest URL changed from {before_url} to ' u'{after_url}').format(before_url=before_url, after_url=after_url)) RereviewQueue.flag(self.instance, mkt.LOG.REREVIEW_MANIFEST_URL_CHANGE, msg) # Refetch the new manifest. log.info('Manifest %s refreshed for %s' % (addon.manifest_url, addon)) update_manifests.delay([self.instance.id]) tags_new = self.cleaned_data['tags'] tags_old = [slugify(t, spaces=True) for t in self.get_tags(addon)] add_tags = set(tags_new) - set(tags_old) del_tags = set(tags_old) - set(tags_new) # Add new tags. for t in add_tags: Tag(tag_text=t).save_tag(addon) # Remove old tags. for t in del_tags: Tag(tag_text=t).remove_tag(addon) return addonform
def yesno(boolean_value): return jinja2.Markup(_lazy(u'Yes') if boolean_value else _lazy(u'No'))
def __init__(self, *args, **kwargs): super(SearchFilter, self).__init__(*args, **kwargs) self.filters['timezone'].field.choices.insert( 0, ('', _lazy(u'All timezones')))
class RegionForm(forms.Form): regions = forms.MultipleChoiceField( required=False, choices=[], widget=forms.CheckboxSelectMultiple, label=_lazy(u'Choose the regions your app will be listed in:'), error_messages={ 'required': _lazy(u'You must select at least one region.') }) enable_new_regions = forms.BooleanField(required=False, label=_lazy(u'Enable new regions')) restricted = forms.TypedChoiceField( required=False, initial=0, coerce=int, 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'})) def __init__(self, *args, **kw): self.product = kw.pop('product', None) self.request = kw.pop('request', None) super(RegionForm, self).__init__(*args, **kw) self.fields['regions'].choices = REGIONS_CHOICES_SORTED_BY_NAME() # This is the list of the user's exclusions as we don't # want the user's choices to be altered by external # exclusions e.g. payments availability. user_exclusions = list( self.product.addonexcludedregion.values_list('region', flat=True)) # If we have excluded regions, uncheck those. # Otherwise, default to everything checked. self.regions_before = self.product.get_region_ids( restofworld=True, excluded=user_exclusions) self.initial = { 'regions': sorted(self.regions_before), 'restricted': int(self.product.geodata.restricted), 'enable_new_regions': self.product.enable_new_regions, } @property def regions_by_id(self): return mkt.regions.REGIONS_CHOICES_ID_DICT def is_toggling(self): if not self.request or not hasattr(self.request, 'POST'): return False return self.product.premium_type != mkt.ADDON_FREE def _product_is_paid(self): return (self.product.premium_type in mkt.ADDON_PREMIUMS or self.product.premium_type == mkt.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']] restricted = int(self.cleaned_data['restricted'] or 0) if restricted: before = set(self.regions_before) after = set(regions) log.info(u'[Webapp:%s] App marked 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 excluded from region (%s).' % (self.product, region)) # If restricted, check how we should handle new 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) else: # If not restricted, set `enable_new_regions` to True and remove # currently excluded regions. self.product.update(enable_new_regions=True) self.product.addonexcludedregion.all().delete() log.info(u'[Webapp:%s] App marked as unrestricted.' % self.product) self.product.geodata.update(restricted=restricted)
class LICENSE_CC_BY_NC_SA(_LicenseBase): id = 5 icons = 'cc-attrib cc-noncom cc-share' name = _lazy(u'Creative Commons Attribution-NonCommercial-Share Alike 3.0') url = 'http://creativecommons.org/licenses/by-nc-sa/3.0/'
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, required=False) 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 = 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): instance = kwargs.get('instance') if instance and instance.has_anonymous_username(): kwargs.setdefault('initial', {}) kwargs['initial']['username'] = '' super(UserRegisterForm, self).__init__(*args, **kwargs) if not settings.NOBOT_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 kitsune.questions.models import Answer, AnswerVote, Question, QuestionLocale, QuestionVote from kitsune.questions.utils import get_mobile_product_from_ua, get_featured_articles from kitsune.sumo.decorators import ratelimit, ssl_required from kitsune.sumo.templatetags.jinja_helpers import urlparams from kitsune.sumo.urlresolvers import reverse, split_path from kitsune.sumo.utils import build_paged_url, is_ratelimited, paginate, simple_paginate from kitsune.tags.utils import add_existing_tag from kitsune.upload.models import ImageAttachment from kitsune.upload.views import upload_imageattachment from kitsune.users.models import Setting from kitsune.wiki.facets import topics_for log = logging.getLogger("k.questions") UNAPPROVED_TAG = _lazy("That tag does not exist.") NO_TAG = _lazy("Please provide a tag.") IMG_LIMIT = settings.IMAGE_ATTACHMENT_USER_LIMIT FILTER_GROUPS = { "all": OrderedDict([ ("recently-unanswered", _lazy("Recently unanswered")), ]), "needs-attention": OrderedDict([ ("new", _lazy("New")), ("unhelpful-answers", _lazy("Answers didn't help")), ]), "responded": OrderedDict([
class GroupBase(models.Model): """Base class for groups in Mozillians.""" name = models.CharField(db_index=True, max_length=100, unique=True, verbose_name=_lazy(u'Name')) url = models.SlugField(blank=True) objects = GroupBaseManager.from_queryset(GroupQuerySet)() class Meta: abstract = True ordering = ['name'] def get_absolute_url(self): cls_name = self.__class__.__name__ url_pattern = 'groups:show_{0}'.format(cls_name.lower()) return absolutify(reverse(url_pattern, args=[self.url])) def clean(self): """Verify that name is unique in ALIAS_MODEL.""" super(GroupBase, self).clean() query = self.ALIAS_MODEL.objects.filter(name=self.name) if self.pk: query = query.exclude(alias=self) if query.exists(): raise ValidationError({'name': _('This name already exists.')}) return self.name @classmethod def search(cls, query): query = query.lower() results = cls.objects.filter(aliases__name__contains=query) results = results.distinct() return results def save(self, *args, **kwargs): """Override save method.""" self.name = self.name.lower() super(GroupBase, self).save() if not self.url: alias = self.ALIAS_MODEL.objects.create(name=self.name, alias=self) self.url = alias.url super(GroupBase, self).save() def __unicode__(self): return self.name def merge_groups(self, group_list): """Merge two groups.""" for group in group_list: map(lambda x: self.add_member(x), group.members.all()) group.aliases.update(alias=self) group.delete() def user_can_leave(self, userprofile): """Checks if a member of a group can leave.""" curators = self.curators.all() return ( # some groups don't allow leaving getattr(self, 'members_can_leave', True) # We need at least one curator and (curators.count() > 1 or userprofile not in curators) # only makes sense to leave a group they belong to (at least pending) and (self.has_member(userprofile=userprofile) or self.has_pending_member(userprofile=userprofile))) def curator_can_leave(self, userprofile): """Check if a curator can leave a group.""" curators = self.curators.all() return curators.count() > 1 or userprofile not in curators def user_can_join(self, userprofile): """Checks if a user can join a group. Each one of the requirements must assert to True. """ can_join_group = all([ # Must be vouched and group must be Open userprofile.is_vouched, getattr(self, 'accepting_new_members', Group.OPEN) != Group.CLOSED, # only makes sense to join if not already a member not self.has_member(userprofile=userprofile), # or the membership is in a pending state not self.has_pending_member(userprofile=userprofile), # If this is an access group the user should be able to join (userprofile.can_join_access_groups() if type(self) is Group and self.is_access_group else True) ]) return can_join_group # Read-only properties so clients don't care which subclasses have some fields @property def is_visible(self): """Checks if a group is visible.""" return getattr(self, 'visible', True) def add_member(self, userprofile): """Adds a method to a group.""" self.members.add(userprofile) def remove_member(self, userprofile): """Removes a member from a group.""" self.members.remove(userprofile) def has_member(self, userprofile): """Checks membership status.""" return self.members.filter(user=userprofile.user).exists() def has_pending_member(self, userprofile): """Checks if a membership is pending. Skills have no pending members, just members """ return False
class Group(GroupBase): """Group class.""" ALIAS_MODEL = GroupAlias # Possible group types OPEN = u'yes' REVIEWED = u'by_request' CLOSED = u'no' GROUP_TYPES = ( (OPEN, _lazy(u'Open')), (REVIEWED, _lazy(u'Reviewed')), (CLOSED, _lazy(u'Closed')), ) ACCESS_GROUP = _lazy(u'Access Group') TAG = _lazy(u'Tag') ACCESS_GROUP_TYPES = ( (True, ACCESS_GROUP), (False, TAG), ) # Has a steward taken ownership of this group? description = models.TextField(max_length=1024, verbose_name=_lazy(u'Description'), default='', blank=True) curators = models.ManyToManyField('users.UserProfile', related_name='groups_curated') irc_channel = models.CharField( max_length=63, verbose_name=_lazy(u'IRC Channel'), help_text=_lazy( u'An IRC channel where this group is discussed (optional).'), default='', blank=True) website = models.URLField( max_length=200, verbose_name=_lazy(u'Website'), help_text=_lazy( u'A URL of a web site with more information about this group (optional).' ), default='', blank=True) wiki = models.URLField( max_length=200, verbose_name=_lazy(u'Wiki'), help_text=_lazy( u'A URL of a wiki with more information about this group (optional).' ), default='', blank=True) members_can_leave = models.BooleanField(default=True) accepting_new_members = models.CharField( verbose_name=_lazy(u'Accepting new members'), choices=GROUP_TYPES, default=OPEN, max_length=10) new_member_criteria = models.TextField( max_length=1024, default='', blank=True, verbose_name=_lazy(u'New Member Criteria'), help_text=_lazy( u'Specify the criteria you will use to decide whether or not ' u'you will accept a membership request.')) functional_area = models.BooleanField(default=False) visible = models.BooleanField( default=True, help_text=_lazy( u'Whether group is shown on the UI (in group lists, search, etc). Mainly ' u'intended to keep system groups like "staff" from cluttering up the ' u'interface.')) max_reminder = models.IntegerField( default=0, help_text= (u'The max PK of pending membership requests the last time we sent the ' u'curator a reminder')) terms = models.TextField(default='', verbose_name=_('Terms'), blank=True) invalidation_days = models.PositiveIntegerField( null=True, default=None, blank=True, verbose_name=_('Invalidation days')) invites = models.ManyToManyField('users.UserProfile', related_name='invites_received', through='Invite', through_fields=('group', 'redeemer')) invite_email_text = models.TextField( max_length=2048, default='', blank=True, help_text=_('Please enter any additional text for the ' 'invitation email')) is_access_group = models.BooleanField( default=False, choices=ACCESS_GROUP_TYPES, verbose_name='Is this an access group?') objects = GroupManager.from_queryset(GroupQuerySet)() @classmethod def get_functional_areas(cls): """Return all visible groups that are functional areas.""" return cls.objects.visible().filter(functional_area=True) @classmethod def get_non_functional_areas(cls, **kwargs): """ Return all visible groups that are not functional areas. Use kwargs to apply additional filtering to the groups. """ return cls.objects.visible().filter(functional_area=False, **kwargs) @classmethod def get_curated(cls): """Return all non-functional areas that are curated.""" return cls.get_non_functional_areas(curators__isnull=False) @classmethod def search(cls, query): return super(Group, cls).search(query).visible() def merge_groups(self, group_list): for membership in GroupMembership.objects.filter(group__in=group_list): # add_member will never demote someone, so just add them with the current membership # level from the merging group and they'll end up with the highest level from # either group. self.add_member(membership.userprofile, membership.status) for group in group_list: group.aliases.update(alias=self) group.delete() def add_member(self, userprofile, status=GroupMembership.MEMBER, inviter=None): """ Add a user to this group. Optionally specify status other than member. If user is already in the group with the given status, this is a no-op. If user is already in the group with a different status, their status will be updated if the change is a promotion. Otherwise, their status will not change. If the group in question is the NDA group, also add the user to the NDA newsletter. """ defaults = dict(status=status, date_joined=now()) membership, _ = GroupMembership.objects.get_or_create( userprofile=userprofile, group=self, defaults=defaults) # Remove the need_removal flag in any case # We have a renewal, let's save the object. if membership.needs_renewal: membership.needs_renewal = False membership.save() if membership.status != status: # Status changed # The only valid status change states are: # PENDING to MEMBER # PENDING to PENDING_TERMS # PENDING_TERMS to MEMBER old_status = membership.status membership.status = status statuses = [ (GroupMembership.PENDING, GroupMembership.MEMBER), (GroupMembership.PENDING, GroupMembership.PENDING_TERMS), (GroupMembership.PENDING_TERMS, GroupMembership.MEMBER) ] if (old_status, status) in statuses: # Status changed membership.save() if inviter: # Set the invite to the last person who renewed the membership invite = get_object_or_none(Invite, group=membership.group, redeemer=userprofile) if invite: invite.inviter = inviter invite.save() def remove_member(self, userprofile, status=None, send_email=False): """Change membership status for a group. If user is a member of an open group, then the user is removed. If a user is a member of a reviewed or closed group, then the membership is in a pending state. """ # Avoid circular dependencies from mozillians.users.models import UserProfile try: membership = GroupMembership.objects.get(group=self, userprofile=userprofile) except GroupMembership.DoesNotExist: return old_status = membership.status # If the group is of type Group.OPEN, delete membership # If no status is given, delete membership, # If the current membership is PENDING*, delete membership if (not status or self.accepting_new_members == Group.OPEN or old_status != GroupMembership.MEMBER): # We have either an open group or the request to join a reviewed group is denied # or the curator manually declined a user in a pending state. membership.delete() # delete the invitation to the group if exists Invite.objects.filter(group=self, redeemer=userprofile).delete() send_email = True # Remove all the access groups the user is a member of # if the group to remove is the NDA if not GroupMembership.objects.filter( userprofile=userprofile, group__name__in=settings.NDA_ACCESS_GROUPS): group_memberships = GroupMembership.objects.none() # If the user is not staff, we need to delete the memberships to any access group if not userprofile.can_create_access_groups: group_memberships = GroupMembership.objects.filter( userprofile=userprofile, group__is_access_group=True) for access_membership in group_memberships: group = access_membership.group if not group.curator_can_leave(userprofile): # If the user is the only curator, let's add the superusers as curators # as a fallback option for super_user in UserProfile.objects.filter( user__is_superuser=True): group.curators.add(super_user) if not group.has_member(super_user): group.add_member(super_user) group.curators.remove(userprofile) access_membership.delete() # Group is either of Group.REVIEWED or Group.CLOSED, change membership to `status` else: # if we are here, there is a new status for our user membership.status = status membership.needs_renewal = False membership.save() def has_member(self, userprofile): """ Return True if this user is in this group with status MEMBER. """ return self.groupmembership_set.filter( userprofile=userprofile, status=GroupMembership.MEMBER).exists() def has_pending_member(self, userprofile): """ Return True if this user is in this group with status PENDING or there is a flag marking the profile ready for a renewal """ return (self.groupmembership_set.filter( userprofile=userprofile).filter( Q(status=GroupMembership.PENDING) | Q(needs_renewal=True))).exists()
class UserProfile(UserProfilePrivacyModel): objects = ProfileManager() 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) bio = models.TextField(verbose_name=_lazy(u'Bio'), default='', blank=True) photo = ImageField(default='', blank=True, upload_to=_calculate_photo_filename) # 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) # django-cities-light fields city = models.ForeignKey('cities_light.City', blank=True, null=True, on_delete=models.SET_NULL) region = models.ForeignKey('cities_light.Region', blank=True, null=True, on_delete=models.SET_NULL) country = models.ForeignKey('cities_light.Country', blank=True, null=True, on_delete=models.SET_NULL) 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)) 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='') # This is the Auth0 user ID. We are saving only the primary here. auth0_user_id = models.CharField(max_length=1024, default='', blank=True) is_staff = models.BooleanField(default=False) 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', 'identity_profiles': '_identity_profiles' } 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 _identity_profiles(self): _getattr = (lambda x: super(UserProfile, self).__getattribute__(x)) accounts = _getattr('idp_profiles').all() 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: # Try IDP contact first if self.idp_profiles.exists(): contact_ids = self.identity_profiles.filter( primary_contact_identity=True) if contact_ids.exists(): return contact_ids[0].email return '' # Fallback to user.email if _getattr('privacy_email') < self._privacy_level: return privacy_fields['email'] # In case we don't have a privacy aware attribute access if self.idp_profiles.filter(primary_contact_identity=True).exists(): return self.idp_profiles.filter( primary_contact_identity=True)[0].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.is_superuser: return PRIVATE 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.""" # TODO needs update 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 @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 get_photo_thumbnail(self, geometry='160x160', **kwargs): if 'crop' not in kwargs: kwargs['crop'] = 'center' if self.photo and default_storage.exists(self.photo.name): # Workaround for legacy images in RGBA model try: image_obj = Image.open(self.photo) except IOError: return get_thumbnail(settings.DEFAULT_AVATAR_PATH, geometry, **kwargs) if image_obj.mode == 'RGBA': new_fh = default_storage.open(self.photo.name, 'w') converted_image_obj = image_obj.convert('RGB') converted_image_obj.save(new_fh, 'JPEG') new_fh.close() return get_thumbnail(self.photo, geometry, **kwargs) return get_thumbnail(settings.DEFAULT_AVATAR_PATH.format(), 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.email, size=geometry) photo_url = self.get_photo_thumbnail(geometry, **kwargs).url if photo_url.startswith('https://') or photo_url.startswith('http://'): return photo_url return absolutify(photo_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 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 autovouch = kwargs.pop('autovouch', False) super(UserProfile, self).save(*args, **kwargs) # Auto_vouch follows the first save, because you can't # create foreign keys without a database id. if autovouch: self.auto_vouch()
from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required from django.core.urlresolvers import reverse as django_reverse from django.http import HttpResponsePermanentRedirect, HttpResponseRedirect from django.utils.encoding import iri_to_uri, smart_str from django.utils.translation import ugettext_lazy as _lazy, activate from mozillians.common import urlresolvers from mozillians.common.templatetags.helpers import redirect, urlparams from mozillians.common.urlresolvers import reverse LOGIN_MESSAGE = _lazy(u'You must be logged in to continue.') GET_VOUCHED_MESSAGE = _lazy(u'You must be vouched to continue.') class StrongholdMiddleware(object): """Keep unvouched users out, unless explicitly allowed in. Inspired by https://github.com/mgrouchy/django-stronghold/ """ def __init__(self, get_response): self.get_response = get_response self.exceptions = getattr(settings, 'STRONGHOLD_EXCEPTIONS', []) def __call__(self, request):
class LICENSE_CC_BY_SA(_LicenseBase): id = 7 icons = 'cc-attrib cc-share' name = _lazy(u'Creative Commons Attribution-ShareAlike 3.0') url = 'http://creativecommons.org/licenses/by-sa/3.0/'
class LICENSE_CC_BY(_LicenseBase): id = 2 name = _lazy(u'Creative Commons Attribution 3.0') url = 'http://creativecommons.org/licenses/by/3.0/' icons = 'cc-attrib'
class LICENSE_CC_BY_ND(_LicenseBase): id = 6 icons = 'cc-attrib cc-noderiv' name = _lazy(u'Creative Commons Attribution-NoDerivs 3.0') url = 'http://creativecommons.org/licenses/by-nd/3.0/'
class LICENSE_COPYRIGHT(_LicenseBase): id = 1 name = _lazy(u'All Rights Reserved') icons = 'copyr' some_rights = False url = None
class LICENSE_CC_BY_NC(_LicenseBase): id = 3 icons = 'cc-attrib cc-noncom' name = _lazy(u'Creative Commons Attribution-NonCommercial 3.0') url = 'http://creativecommons.org/licenses/by-nc/3.0/'
class PublishForm(happyforms.Form): # Publish choice wording is slightly different here than with the # submission flow because the app may have already been published. mark_safe_lazy = lazy(mark_safe, six.text_type) PUBLISH_CHOICES = ( (mkt.PUBLISH_IMMEDIATE, mark_safe_lazy( _lazy( u'<b>Published</b>: Visible to everyone in the Marketplace and ' u'included in search results and listing pages.'))), (mkt.PUBLISH_HIDDEN, mark_safe_lazy( _lazy(u'<b>Unlisted</b>: Visible to only people with the URL and ' u'does not appear in search results and listing pages.'))), ) # Used for setting initial form values. PUBLISH_MAPPING = { mkt.STATUS_PUBLIC: mkt.PUBLISH_IMMEDIATE, mkt.STATUS_UNLISTED: mkt.PUBLISH_HIDDEN, mkt.STATUS_APPROVED: mkt.PUBLISH_PRIVATE, } # Use in form processing to set status. STATUS_MAPPING = dict((v, k) for k, v in PUBLISH_MAPPING.items()) publish_type = forms.TypedChoiceField(required=False, choices=PUBLISH_CHOICES, widget=forms.RadioSelect(), initial=0, coerce=int, label=_lazy('App Visibility:')) limited = forms.BooleanField( required=False, label=_lazy(u'<b>Limit to my team</b>: Visible to only Team Members.')) def __init__(self, *args, **kwargs): self.addon = kwargs.pop('addon') super(PublishForm, self).__init__(*args, **kwargs) limited = False publish = self.PUBLISH_MAPPING.get(self.addon.status, mkt.PUBLISH_IMMEDIATE) if self.addon.status == mkt.STATUS_APPROVED: # Special case if app is currently private. limited = True publish = mkt.PUBLISH_HIDDEN # Determine the current selection via STATUS to publish choice mapping. self.fields['publish_type'].initial = publish self.fields['limited'].initial = limited # Make the limited label safe so we can display the HTML. self.fields['limited'].label = mark_safe(self.fields['limited'].label) def save(self): publish = self.cleaned_data['publish_type'] limited = self.cleaned_data['limited'] if publish == mkt.PUBLISH_HIDDEN and limited: publish = mkt.PUBLISH_PRIVATE status = self.STATUS_MAPPING[publish] self.addon.update(status=status) mkt.log(mkt.LOG.CHANGE_STATUS, self.addon.get_status_display(), self.addon) # Call update_version, so various other bits of data update. self.addon.update_version() # Call to update names and locales if changed. self.addon.update_name_from_package_manifest() self.addon.update_supported_locales() if waffle.switch_is_active('iarc-upgrade-v2'): iarc_publish.delay(self.addon.pk) else: set_storefront_data.delay(self.addon.pk)
class Meta: verbose_name = _lazy('grupo') verbose_name_plural = _lazy('grupos')
class Meta: verbose_name = _lazy('Historial') verbose_name_plural = _lazy('Historiales') ordering = ['-fecha']
super(GroupAvatarForm, self).__init__(*args, **kwargs) self.fields["avatar"].help_text = _("Your avatar will be resized to {size}x{size}").format( size=settings.AVATAR_SIZE ) class Meta(object): model = GroupProfile fields = ["avatar"] def clean_avatar(self): if not ("avatar" in self.cleaned_data and self.cleaned_data["avatar"]): return self.cleaned_data["avatar"] try: check_file_size(self.cleaned_data["avatar"], settings.MAX_AVATAR_FILE_SIZE) except FileTooLargeError as e: raise forms.ValidationError(e.args[0]) return self.cleaned_data["avatar"] USERS_PLACEHOLDER = _lazy("username") class AddUserForm(forms.Form): """Form to add members or leaders to group.""" users = MultiUsernameField( widget=forms.TextInput( attrs={"placeholder": USERS_PLACEHOLDER, "class": "user-autocomplete"} ) )
import logging from collections import OrderedDict from django.conf import settings from django.utils.translation import ugettext_lazy as _lazy from django_statsd.clients import statsd from zendesk import Zendesk, ZendeskError log = logging.getLogger('k.questions.marketplace') MARKETPLACE_CATEGORIES = OrderedDict([ ('payments', _lazy('Payments')), ('applications', _lazy('Applications')), ('account', _lazy('Account')), ]) class ZendeskSettingsError(ZendeskError): """Exception for missing settings.""" def get_zendesk(): """Instantiate and return a Zendesk client""" # Verify required Zendesk settings zendesk_url = settings.ZENDESK_URL zendesk_email = settings.ZENDESK_USER_EMAIL zendesk_password = settings.ZENDESK_USER_PASSWORD if not zendesk_url or not zendesk_email or not zendesk_password: log.error('Zendesk settings error: please set ZENDESK_URL, '
def validate_email(value): if not re.search(EMAIL_REGEX, value): raise ValidationError(_lazy("Invalid Email")) return value
class HistorialEstado(models.Model): """ Modelo para guardar historial de cambio de estado de los grupos. **Significado de cada estado** * ``ACTIVO`` Este estado es aplicado a grupos que realicen todas las funciones que un grupo hace, es decir: reunion de G.A.R, reunion de discipulado, etc. * ``INACTIVO`` Este estado es aplicado a grupos que en la actualidad no se encuentran realizando reuniones de G.A.R, pero realizan encuentros, reuniones de dicipulado, etc. * ``SUSPENDIDO`` Este estado es aplicado a grupos que en la actualidad no realizan ninguna acción de grupos, pero no quiere ser archivado. * ``ARCHIVADO`` Este estado es aplicado a grupos que serán eliminados. **Usabilidad** Solo se podrá tener un ``estado`` diferente de seguido para cada grupo, es decir, no se podrán tener dos estados de ``ACTIVO`` de seguido para el mismo grupo. Ante el caso de una posible repetición de ``estado``, la creación de el historial del estado será abortada. """ ACTIVO = 'AC' INACTIVO = 'IN' SUSPENDIDO = 'SU' ARCHIVADO = 'AR' OPCIONES_ESTADO = ( (ACTIVO, 'ACTIVO'), (INACTIVO, 'INACTIVO'), (SUSPENDIDO, 'SUSPENDIDO'), (ARCHIVADO, 'ARCHIVADO'), ) grupo = models.ForeignKey(Grupo, related_name='historiales', verbose_name=_lazy('grupo')) fecha = models.DateTimeField(verbose_name=_lazy('fecha'), auto_now_add=True) estado = models.CharField(max_length=2, choices=OPCIONES_ESTADO, verbose_name=_lazy('estado')) objects = HistorialManager() def __str__(self): return 'Historial {estado} para grupo: {self.grupo}'.format(self=self, estado=self.get_estado_display()) class Meta: verbose_name = _lazy('Historial') verbose_name_plural = _lazy('Historiales') ordering = ['-fecha'] def save(self, *args, **kwargs): """ Guarda una instancia de EstadoHistorial .. warning:: Si el grupo tiene en su último historial, el mismo estado del historial actual a guardar, entonces, este será omitido. """ with transaction.atomic(): last = self.grupo.historiales.first() if last is None or last.estado != self.estado: if self.estado == self.ARCHIVADO: self.grupo.nombre = '{} (ARCHIVADO)'.format(self.grupo.__str__()) self.grupo.save() return super().save(*args, **kwargs)
class ExternalAccount(models.Model): # Constants for type field values. TYPE_AMO = 'AMO' TYPE_BMO = 'BMO' TYPE_EMAIL = 'EMAIL' TYPE_MDN = 'MDN' TYPE_SUMO = 'SUMO' TYPE_FACEBOOK = 'FACEBOOK' TYPE_TWITTER = 'TWITTER' TYPE_AIM = 'AIM' 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_MOPONTOON = 'MOZILLAPONTOON' TYPE_TRANSIFEX = 'TRANSIFEX' TYPE_TELEGRAM = 'TELEGRAM' TYPE_MASTODON = 'MASTODON' TYPE_DISCORD = 'DISCORD' TYPE_MOZPHAB = 'MOZPHAB' # 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_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_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_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': 'https://www.linkedin.com/in/{identifier}/', 'validator': validate_linkedin }, TYPE_JABBER: { 'name': 'XMPP/Jabber', 'url': '', 'validator': validate_email }, TYPE_MASTODON: { 'name': 'Mastodon', 'url': '', 'validator': validate_email }, TYPE_DISCOURSE: { 'name': 'Mozilla Discourse', 'url': 'https://discourse.mozilla.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_MOPONTOON: { 'name': 'Mozilla Pontoon', 'url': 'https://pontoon.mozilla.org/contributor/{identifier}/', 'validator': validate_email }, TYPE_TRANSIFEX: { 'name': 'Transifex', 'url': 'https://www.transifex.com/accounts/profile/{identifier}/', 'validator': validate_username_not_url }, TYPE_TELEGRAM: { 'name': 'Telegram', 'url': 'https://telegram.me/{identifier}', 'validator': validate_username_not_url }, TYPE_DISCORD: { 'name': 'Discord', 'url': '', 'validator': validate_discord }, TYPE_MOZPHAB: { 'name': 'Mozilla Phabricator', 'url': 'https://phabricator.services.mozilla.com/p/{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_WITH_PRIVATE) 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)) if self.type == 'LINKEDIN' and '://' in self.identifier: return 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) def __unicode__(self): return self.type
class Grupo(DiasSemanaMixin, SixALNode, AL_Node): """ Modelo para guardar la información de los grupos de una iglesia. """ ACTIVO = 'A' INACTIVO = 'I' ESTADOS = ( (ACTIVO, 'Activo'), (INACTIVO, 'Inactivo'), ) parent = models.ForeignKey( 'self', verbose_name=_lazy('grupo origen'), related_name='children_set', null=True, db_index=True ) direccion = models.CharField(verbose_name=_lazy('dirección'), max_length=50) fechaApertura = models.DateField(verbose_name=_lazy('fecha de apertura')) diaGAR = models.CharField(verbose_name=_lazy('dia G.A.R'), max_length=1, choices=DiasSemanaMixin.DIAS_SEMANA) horaGAR = models.TimeField(verbose_name=_lazy('hora G.A.R')) diaDiscipulado = models.CharField( verbose_name=_lazy('dia discipulado'), max_length=1, choices=DiasSemanaMixin.DIAS_SEMANA, blank=True, null=True ) horaDiscipulado = models.TimeField(verbose_name=_lazy('hora discipulado'), blank=True, null=True) nombre = models.CharField(verbose_name=_lazy('nombre'), max_length=255) red = models.ForeignKey(Red, verbose_name=('red'), null=True, blank=True) barrio = models.ForeignKey('miembros.Barrio', verbose_name=_lazy('barrio')) # campos para ubicaciones en mapas latitud = models.FloatField(verbose_name='Latitud', blank=True, null=True) longitud = models.FloatField(verbose_name='Longitud', blank=True, null=True) # managers _objects = GrupoManagerStandard() # contiene los defaults de django objects = GrupoManager() node_order_by = ['id'] class Meta: verbose_name = _lazy('grupo') verbose_name_plural = _lazy('grupos') def __str__(self): if self.estado == self.__class__.objects.get_queryset().get_historial_model().ARCHIVADO: return self.get_nombre() lideres = ["{0} {1}({2})".format( lider.nombre.upper(), lider.primer_apellido.upper(), lider.cedula ) for lider in self.lideres.all()] return " - ".join(lideres) def save(self, *args, **kwargs): super().save(*args, **kwargs) if not self.historiales.exists(): self.actualizar_estado(estado=self.__class__.objects.get_queryset().get_historial_model().ACTIVO) @classmethod def _obtener_arbol_recursivamente(cls, padre, resultado): """ Construye el organigrama de forma recursiva. """ lista_hijos = [] for hijo in padre.get_children().prefetch_related('lideres'): cls._obtener_arbol_recursivamente(hijo, lista_hijos) resultado.append(padre) if lista_hijos: resultado.append(lista_hijos) @classmethod def obtener_arbol(cls, padre=None): """ :returns: El organigrama en una lista de listas incluyendo el padre, que me indica como va el desarrollo de los grupos. :param padre: El grupo inicial desde donde sa va mostrar el organigrama. Si no se indica se muestra todo el organigrama de una iglesia. """ # TODO Mejorar en la doc que retorna con un ejemplo usar como guia el metodo get get_annotated_list de # http://django-treebeard.readthedocs.io/en/latest/api.html#treebeard.models.Node.add_child arbol = [] if padre is None: padre = cls.objects.raiz() if padre is not None: cls._obtener_arbol_recursivamente(padre, arbol) return arbol # Deprecado @classmethod def obtener_arbol_viejo(cls, raiz=None): # pragma: no cover """ :returns: El arbol en una lista de listas incluyendo el padre, que me indica como va el desarrollo de los grupos. """ arbol = [] if raiz is None: raiz = cls.objects.raiz() if raiz is not None: pila = [[raiz]] act = None bajada = True discipulos = list(raiz.get_children().select_related('parent').prefetch_related('lideres')) while len(discipulos) > 0: hijo = discipulos.pop() if hijo: if act is not None: pila.append(act) sw = True while len(pila) > 0 and sw: act = pila.pop() if act[len(act) - 1] == hijo.parent: act.append([hijo]) bajada = True sw = False elif act[len(act) - 2] == hijo.parent: act[len(act) - 1].append(hijo) bajada = True sw = False elif isinstance(act[-1], (tuple, list)) and bajada: pila.append(act) pila.append(act[len(act) - 1]) elif not isinstance(act[-1], (tuple, list)): bajada = False hijos = hijo.get_children().select_related('parent').prefetch_related('lideres') if len(hijos) > 0: discipulos.extend(list(hijos)) if pila: arbol = pila[0] else: arbol = act return arbol @classmethod def obtener_ruta(cls, inicial, final): """ :returns: Una lista con los grupos que conforman la ruta que hay desde el grupo inicial al grupo final incluyendo estos grupos. :param inicial: Grupo en cual inicia la ruta. :param final: Grupo en el cual termina la ruta. """ ruta = [] grupo = final while grupo != inicial: ruta.insert(0, grupo) grupo = grupo.get_parent() ruta.insert(0, inicial) return ruta @property def discipulos(self): """ :returns: Un queryset con los miembros del grupo actual que son lideres. .. note:: Es considerado lider todo miembro que tenga permiso de líder. """ return self.miembros.lideres() @property def reuniones_GAR_sin_ofrenda_confirmada(self): """ :returns: Un queryset con las reuniones GAR del grupo actual que no tienen la ofrenda confirmada. """ return self.reuniones_gar.filter(confirmacionEntregaOfrenda=False) @property def reuniones_discipulado_sin_ofrenda_confirmada(self): """ :returns: Un queryset con las reuniones de discipulado del grupo actual que no tienen la ofrenda confirmada. """ return self.reuniones_discipulado.filter(confirmacionEntregaOfrenda=False) @property def grupos_red(self): """ :returns: Un queryset con la subred del grupo actual. .. note:: Entiéndase por subred todos los descendientes del grupo actual incluyéndose asimismo. """ from .utils import convertir_lista_grupos_a_queryset return convertir_lista_grupos_a_queryset(self.get_tree(self)) @property @cache_value(key='cabeza_red', suffix='pk') def cabeza_red(self): """ :returns: El grupo cabeza de red del grupo actual o ``None`` si el grupo actual se encuentra por encima de los cabezas de red. .. note:: Entiéndase por cabeza de red un grupo que se encuentra dentro de los 72 del pastor presidente, es decir, un grupo que se encuentra dos niveles mas abajo que la raiz del arbol. """ ancestros = self.get_ancestors() if self.get_depth() == 3: return self elif len(ancestros) > 2: return ancestros[2] else: return None @property @cache_value(key='numero_celulas', suffix='pk') def numero_celulas(self): """ :returns: El numero de grupos a cargo que tiene el grupo actual, incluyendose el mismo. """ return self.get_descendant_count() + 1 @property def estado(self): """ Retorna el estado del grupo de acuerdo a su historial. :rtype: str """ return getattr(self.historiales.first(), 'estado', 'NONE') @property def is_activo(self): """ :returns: ``True`` si el estado del grupo es activo. :rtype: bool """ return self.estado == self.historiales.model.ACTIVO def get_estado_display(self): """ :returns: Estado display del grupo de acuerdo a su historial. """ return self.historiales.first().get_estado_display() def confirmar_ofrenda_reuniones_GAR(self, reuniones): """ Confirma la ofrenda de las reuniones GAR ingresadas del grupo actual. :param list[int] reuniones: Contiene los ids de las reuniones a confirmar. """ self.reuniones_gar.filter(id__in=reuniones).update(confirmacionEntregaOfrenda=True) def confirmar_ofrenda_reuniones_discipulado(self, reuniones): """ Confirma la ofrenda de las reuniones de discipulado ingresadas del grupo actual. :param list[int] reuniones: Contiene los ids de las reuniones a confirmar. """ self.reuniones_discipulado.filter(id__in=reuniones).update(confirmacionEntregaOfrenda=True) def trasladar(self, nuevo_padre): """ Traslada el grupo actual y sus descendientes debajo de un nuevo grupo en el arbol. A los lideres del grupo actual, se les modifica el grupo al que pertenecen, al nuevo grupo. :param nuevo_padre: Grupo que va ser usado como nuevo padre del grupo actual que se esta trasladando. :raise InvalidMoveToDescendant: Cuando se intenta trasladar el grupo actual debajo de uno de sus descendientes. """ with transaction.atomic(): if nuevo_padre != self.parent: self.move(nuevo_padre, pos='sorted-child') self.lideres.all().update(grupo=nuevo_padre) if nuevo_padre.red != self.red: grupos = [grupo.id for grupo in self._get_tree(self)] Grupo._objects.filter(id__in=grupos).update(red=nuevo_padre.red) def _trasladar_miembros(self, nuevo_grupo): """ Traslada todos los miembros que no lideran grupo del grupo actual al nuevo grupo. :param nuevo_grupo: Grupo al cual van a pertenecer los miembros que se van a trasladar. """ self.miembros.filter(grupo_lidera=None).update(grupo=nuevo_grupo) def _trasladar_visitas(self, nuevo_grupo): """ Traslada todas las visitas del grupo actual al nuevo grupo. :param nuevo_grupo: Grupo al cual van a pertenecer las visitas que se van a trasladar. """ self.visitas.update(grupo=nuevo_grupo) def _trasladar_encontristas(self, nuevo_grupo): """ Traslada todos los encontristas del grupo actual al nuevo grupo. :param nuevo_grupo: Grupo al cual van a pertenecer los encontristas que se van a trasladar. """ self.encontristas.update(grupo=nuevo_grupo) def _trasladar_reuniones_gar(self, nuevo_grupo): """ Traslada todas las reuniones GAR del grupo actual al nuevo grupo. :param nuevo_grupo: Grupo al cual van a pertenecer las reuniones GAR que se van a trasladar. """ self.reuniones_gar.update(grupo=nuevo_grupo) def _trasladar_reuniones_discipulado(self, nuevo_grupo): """ Traslada todas las reuniones de discipulado del grupo actual al nuevo grupo. :param nuevo_grupo: Grupo al cual van a pertenecer las reuniones de discipulado que se van a trasladar. """ self.reuniones_discipulado.update(grupo=nuevo_grupo) def fusionar(self, nuevo_grupo): """ Traslada la información asociada al grupo actual (visitas, encontristas, reuniones, miembros, etc) al nuevo grupo y elimina el grupo actual. :param nuevo_grupo: Grupo al cual va a pertenecer la información que se va a trasladar. """ if self != nuevo_grupo: with transaction.atomic(): for hijo in self.get_children(): hijo.trasladar(nuevo_grupo) self._trasladar_visitas(nuevo_grupo) self._trasladar_miembros(nuevo_grupo) self._trasladar_encontristas(nuevo_grupo) self._trasladar_reuniones_gar(nuevo_grupo) self._trasladar_reuniones_discipulado(nuevo_grupo) self.delete() def get_nombre(self): """ Retorna el nombre del grupo. :rtype: str """ return self.nombre def miembrosGrupo(self): """Devuelve los miembros de un grupo (queryset) sino tiene, devuelve el queryset vacio.""" lideres = CambioTipo.objects.filter(nuevoTipo__nombre__iexact='lider').values('miembro') miembros = CambioTipo.objects.filter(nuevoTipo__nombre__iexact='miembro').values('miembro') return self.miembros.filter(id__in=miembros).exclude(id__in=lideres) def get_direccion(self): """ :returns: La direccion de manera legible para los buscadores de mapas """ if self.get_position() is None: return clean_direccion(self.direccion) else: return ','.join([str(x) for x in self.get_position()]) def get_position(self): """ :returns: Las coordenadas de un grupo o `None` en caso de no tener last coordenadas. """ if self.latitud is not None and self.longitud is not None: return [self.latitud, self.longitud] return None def actualizar_estado(self, estado, **kwargs): """ Actualiza el estado de el grupo en su historial. :param estado: El estado que se asignara. """ actual = self.historiales.first() if actual is None: self.__class__.objects.get_queryset().get_historial_model().objects.create( grupo=self, estado=estado, **kwargs) elif estado != actual.estado: if estado not in list(map(lambda x: x[0], actual.OPCIONES_ESTADO)): raise OperationalError(_lazy('Estado "{}" no está permitido'.format(estado))) actual.__class__.objects.create(grupo=self, estado=estado, **kwargs)
class UserForm(UserCreationForm): first_name = forms.CharField( label=_lazy("First Name"), validators=[ RegexValidator( regex="^([A-ZŁŚĆĄŻŹÓĆŃĘ]){1}([a-zążźśęćńół]){2,30}$", message=_lazy( "Name should have first letter upper case and the rest lower case" ), ) ], widget=forms.Textarea( attrs={ "cols": 5, "rows": 1, "class": "form-control", "style": "resize:none;", }), ) last_name = forms.CharField( label=_lazy("Last Name"), validators=[ RegexValidator( regex="^([A-ZŁŚĆĄŻŹÓĆŃĘ]){1}([a-zążźśęćńół]){2,30}$", message=_lazy( "Surname should have first letter upper case and the rest lower case" ), ) ], widget=forms.Textarea( attrs={ "cols": 5, "rows": 1, "class": "form-control", "style": "resize:none;", }), ) class Meta: model = User fields = [ "username", "password1", "password2", "first_name", "last_name", "email", ] widgets = { "username": forms.Textarea( attrs={ "cols": 5, "rows": 1, "class": "form-control", "style": "resize:none;", }), "email": forms.Textarea( attrs={ "cols": 5, "rows": 1, "class": "form-control", "style": "resize:none;", }), } def __init__(self, *args, **kwargs): super(UserForm, self).__init__(*args, **kwargs) self.fields["username"].help_text = None self.fields["password1"].help_text = None self.fields["password2"].help_text = None self.fields["password1"].widget = forms.PasswordInput( attrs={"class": "form-control"}) self.fields["password2"].widget = forms.PasswordInput( attrs={"class": "form-control"})
def get_actions(self, request, addon): actions = SortedDict() if request is None: # If request is not set, it means we are just (ab)using the # ReviewHelper for its `handler` attribute and we don't care about # the actions. return actions latest_version = addon.find_latest_version( channel=amo.RELEASE_CHANNEL_LISTED) reviewable_because_complete = addon.status not in ( amo.STATUS_NULL, amo.STATUS_DELETED) reviewable_because_admin = ( not addon.admin_review or acl.action_allowed(request, 'ReviewerAdminTools', 'View')) reviewable_because_submission_time = ( not is_limited_reviewer(request) or (latest_version is not None and latest_version.nomination is not None and (datetime.datetime.now() - latest_version.nomination >= datetime.timedelta(hours=REVIEW_LIMITED_DELAY_HOURS)))) reviewable_because_pending = ( latest_version is not None and len(latest_version.is_unreviewed) > 0) if (reviewable_because_complete and reviewable_because_admin and reviewable_because_submission_time and reviewable_because_pending): actions['public'] = { 'method': self.handler.process_public, 'minimal': False, 'details': _lazy('This will approve, sign, and publish this ' 'version. The comments will be sent to the ' 'developer.'), 'label': _lazy('Approve')} actions['reject'] = { 'method': self.handler.process_sandbox, 'label': _lazy('Reject'), 'details': _lazy('This will reject this version and remove it ' 'from the queue. The comments will be sent ' 'to the developer.'), 'minimal': False} actions['info'] = { 'method': self.handler.request_information, 'label': _lazy('Request more information'), 'details': _lazy('This will request more information from the ' 'developer. You will be notified when they ' 'reply.'), 'minimal': True} actions['super'] = { 'method': self.handler.process_super_review, 'label': _lazy('Request super-review'), 'details': _lazy('If you have concerns about this add-on that an ' 'admin reviewer should look into, enter your ' 'comments in the area below. They will not be ' 'sent to the developer.'), 'minimal': True} actions['comment'] = { 'method': self.handler.process_comment, 'label': _lazy('Comment'), 'details': _lazy('Make a comment on this version. The developer ' 'won\'t be able to see this.'), 'minimal': True} return actions
class LicenseForm(AMOModelForm): builtin = forms.TypedChoiceField( choices=[], coerce=int, widget=LicenseRadioSelect(attrs={'class': 'license'})) name = forms.CharField(widget=TranslationTextInput(), label=_lazy(u"What is your license's name?"), required=False, initial=_lazy('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: self.version = addon.latest_version 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 if addon and not addon.is_listed: self.fields['builtin'].required = False 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 == '': # No license chosen, it must be an unlisted add-on. return 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 UserEditForm(UserRegisterForm, PasswordMixin): oldpassword = forms.CharField( max_length=255, required=False, widget=forms.PasswordInput(render_value=False)) password = forms.CharField(max_length=255, required=False, min_length=PasswordMixin.min_length, error_messages=PasswordMixin.error_msg, widget=PasswordMixin.widget(render_value=False)) password2 = forms.CharField(max_length=255, required=False, widget=forms.PasswordInput(render_value=False)) photo = forms.FileField(label=_lazy(u'Profile Photo'), required=False) notifications = forms.MultipleChoiceField( choices=[], widget=NotificationsSelectMultiple, initial=email.NOTIFICATIONS_DEFAULT, required=False) lang = forms.TypedChoiceField(label=_lazy(u'Default locale'), choices=LOCALES) def __init__(self, *args, **kwargs): self.request = kwargs.pop('request', None) super(UserEditForm, self).__init__(*args, **kwargs) if not self.instance.lang and self.request: self.initial['lang'] = self.request.LANG if self.instance: default = dict((i, n.default_checked) for i, n in email.NOTIFICATIONS_BY_ID.items()) user = dict((n.notification_id, n.enabled) for n in self.instance.notifications.all()) default.update(user) # Add choices to Notification. choices = email.NOTIFICATIONS_CHOICES if not self.instance.is_developer: choices = email.NOTIFICATIONS_CHOICES_NOT_DEV if self.instance.fxa_migrated(): self.fields['email'].required = False self.fields['email'].widget = forms.EmailInput( attrs={'readonly': 'readonly'}) self.fields['email'].help_text = fxa_error_message( _(u'Firefox Accounts users cannot currently change their ' u'email address.')) # Append a "NEW" message to new notification options. saved = self.instance.notifications.values_list('notification_id', flat=True) self.choices_status = {} for idx, label in choices: self.choices_status[idx] = idx not in saved self.fields['notifications'].choices = choices self.fields['notifications'].initial = [ i for i, v in default.items() if v ] self.fields['notifications'].widget.form_instance = self # TODO: We should inherit from a base form not UserRegisterForm if self.fields.get('recaptcha'): del self.fields['recaptcha'] class Meta: model = UserProfile exclude = ('password', 'picture_type', 'last_login', 'fxa_id', 'read_dev_agreement') def clean(self): data = self.cleaned_data amouser = self.request.user # Passwords p1 = data.get("password") p2 = data.get("password2") if p1 or p2: if not amouser.check_password(data["oldpassword"]): msg = _("Wrong password entered!") self._errors["oldpassword"] = ErrorList([msg]) del data["oldpassword"] super(UserEditForm, self).clean() return data def clean_email(self): email = self.cleaned_data.get('email') if self.instance.fxa_migrated(): if not email or email == self.instance.email: return self.instance.email else: raise forms.ValidationError(_(u'Email cannot be changed.')) else: return email def clean_photo(self): photo = self.cleaned_data['photo'] if not photo: return if photo.content_type not in ('image/png', 'image/jpeg'): raise forms.ValidationError(_('Images must be either PNG or JPG.')) if photo.size > settings.MAX_PHOTO_UPLOAD_SIZE: raise forms.ValidationError( _('Please use images smaller than %dMB.' % (settings.MAX_PHOTO_UPLOAD_SIZE / 1024 / 1024 - 1))) return photo def clean_bio(self): bio = self.cleaned_data['bio'] normalized = clean_nl(unicode(bio)) if has_links(normalized): # There's some links, we don't want them. raise forms.ValidationError(_('No links are allowed.')) return bio def save(self, log_for_developer=True): u = super(UserEditForm, self).save(commit=False) data = self.cleaned_data photo = data['photo'] if photo: u.picture_type = 'image/png' tmp_destination = u.picture_path + '__unconverted' with storage.open(tmp_destination, 'wb') as fh: for chunk in photo.chunks(): fh.write(chunk) tasks.resize_photo.delay(tmp_destination, u.picture_path, set_modified_on=[u]) if data['password']: u.set_password(data['password']) log_cef('Password Changed', 5, self.request, username=u.username, signature='PASSWORDCHANGED', msg='User changed password') if log_for_developer: amo.log(amo.LOG.CHANGE_PASSWORD) log.info(u'User (%s) changed their password' % u) for (i, n) in email.NOTIFICATIONS_BY_ID.items(): enabled = n.mandatory or (str(i) in data['notifications']) UserNotification.update_or_create(user=u, notification_id=i, update={'enabled': enabled}) log.debug(u'User (%s) updated their profile' % u) u.save() return u