Exemplo n.º 1
0
class UserProfile(UserProfilePrivacyModel):
    REFERRAL_SOURCE_CHOICES = (
        ('direct', 'Mozillians'),
        ('contribute', 'Get Involved'),
    )

    objects = UserProfileManager.from_queryset(UserProfileQuerySet)()

    user = models.OneToOneField(User)
    full_name = models.CharField(max_length=255,
                                 default='',
                                 blank=False,
                                 verbose_name=_lazy(u'Full Name'))
    full_name_local = models.CharField(
        max_length=255,
        blank=True,
        default='',
        verbose_name=_lazy(u'Name in local language'))
    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)
    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)

    # 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)

    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."""
        # 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 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 absolutify(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."""
        emails = [
            acc.identifier for acc in ExternalAccount.objects.filter(
                user=self, type=ExternalAccount.TYPE_EMAIL)
        ]
        emails.append(self.user.email)

        email_exists = any([
            email for email in emails
            if email.split('@')[1] in settings.AUTO_VOUCH_DOMAINS
        ])
        if email_exists and 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('&#34;', '"').replace('&#39;', "'")
        send_mail(subject, filtered_message, settings.FROM_NOREPLY,
                  [self.user.email])

    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)
            group.pending_terms = (
                membership.status == GroupMembership.PENDING_TERMS)
            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
        autovouch = kwargs.pop('autovouch', True)

        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()
Exemplo n.º 2
0
class UserProfile(UserProfilePrivacyModel):
    REFERRAL_SOURCE_CHOICES = (
        ('direct', 'Mozillians'),
        ('contribute', 'Get Involved'),
    )

    objects = UserProfileManager.from_queryset(UserProfileQuerySet)()

    user = models.OneToOneField(User)
    full_name = models.CharField(max_length=255,
                                 default='',
                                 blank=False,
                                 verbose_name=_lazy(u'Full Name'))
    full_name_local = models.CharField(
        max_length=255,
        blank=True,
        default='',
        verbose_name=_lazy(u'Name in local language'))
    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)
    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)

    # 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)

    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',
            '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 _api_alternate_emails(self):
        """
        Helper private property that creates a compatibility layer
        for API results in alternate emails. Combines both IdpProfile
        and ExternalAccount objects. In conflicts/duplicates it returns
        the minimum privacy level defined.
        """
        legacy_emails_qs = self._alternate_emails
        idp_qs = self._identity_profiles

        e_exclude = [
            e.id for e in legacy_emails_qs if idp_qs.filter(
                email=e.identifier, privacy__gte=e.privacy).exists()
        ]
        legacy_emails_qs = legacy_emails_qs.exclude(id__in=e_exclude)

        idp_exclude = [
            i.id for i in idp_qs
            if legacy_emails_qs.filter(identifier=i.email,
                                       privacy__gte=i.privacy).exists()
        ]
        idp_qs = idp_qs.exclude(id__in=idp_exclude)

        return chain(legacy_emails_qs, idp_qs)

    @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.groups.filter(name='Managers').exists()
                or 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 or self.user.groups.filter(
            name='Managers').exists()

    @property
    def is_nda(self):
        query = {
            'userprofile__pk': self.pk,
            'group__name': settings.NDA_GROUP,
            'status': GroupMembership.MEMBER
        }
        return GroupMembership.objects.filter(
            **query).exists() or 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

    @property
    def can_create_access_groups(self):
        """Check if a user can provision access groups.

        An access group is provisioned if a user holds an email in the AUTO_VOUCH_DOMAINS
        and has an LDAP IdpProfile or the user has a superuser account.
        """
        emails = set([
            idp.email
            for idp in IdpProfile.objects.filter(profile=self,
                                                 type=IdpProfile.PROVIDER_LDAP)
            if idp.email.split('@')[1] in settings.AUTO_VOUCH_DOMAINS
        ])
        if self.user.is_superuser or emails:
            return True
        return False

    def can_join_access_groups(self):
        """Check if a user can join access groups.

        A user can join an access group only if has an MFA account and
        belongs to the NDA group or is an employee.
        """
        if self.can_create_access_groups or self.is_nda:
            return True
        return False

    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 and default_storage.exists(self.photo):
            # 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, 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 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."""
        emails = [
            acc.identifier for acc in ExternalAccount.objects.filter(
                user=self, type=ExternalAccount.TYPE_EMAIL)
        ]
        emails.append(self.email)

        email_exists = any([
            email for email in emails
            if email.split('@')[1] in settings.AUTO_VOUCH_DOMAINS
        ])
        if email_exists and 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('&#34;', '"').replace('&#39;', "'")
        send_mail(subject, filtered_message, settings.FROM_NOREPLY,
                  [self.email])

    def _get_annotated_groups(self):
        # 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
        user_group_ids = []
        if hasattr(groups_manager, 'visible'):
            user_group_ids = groups_manager.visible().values_list('id',
                                                                  flat=True)

        return self.groupmembership_set.filter(group__id__in=user_group_ids)

    def get_annotated_tags(self):
        """
        Return a list of all the visible tags 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.
        """
        tags = self._get_annotated_groups().filter(
            group__is_access_group=False)
        annotated_tags = []
        for membership in tags:
            tag = membership.group
            tag.pending = (membership.status == GroupMembership.PENDING)
            tag.pending_terms = (
                membership.status == GroupMembership.PENDING_TERMS)
            annotated_tags.append(tag)
        return annotated_tags

    def get_annotated_access_groups(self):
        """
        Return a list of all the visible access 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. There is also an inviter attribute
        which displays the inviter of the user in the group.
        """
        access_groups = self._get_annotated_groups().filter(
            group__is_access_group=True)
        annotated_access_groups = []

        for membership in access_groups:
            group = membership.group
            group.pending = (membership.status == GroupMembership.PENDING)
            group.pending_terms = (
                membership.status == GroupMembership.PENDING_TERMS)

            try:
                invite = Invite.objects.get(group=membership.group,
                                            redeemer=self)
            except Invite.DoesNotExist:
                invite = None

            if invite:
                group.inviter = invite.inviter
            annotated_access_groups.append(group)

        return annotated_access_groups

    def get_cis_emails(self):
        """Prepares the entry for emails in the CIS format."""
        idp_profiles = self.idp_profiles.all()
        primary_idp = idp_profiles.filter(primary=True)
        emails = []
        primary_email = {
            'value': self.email,
            'verified': True,
            'primary': True,
            'name': 'mozillians-primary-{0}'.format(self.pk)
        }
        # We have an IdpProfile marked as primary (login identity)
        # If there is not an idp profile, the self.email is the one that is used to login
        if primary_idp.exists():
            primary_email['value'] = primary_idp[0].email
            primary_email['name'] = primary_idp[0].get_type_display()

        emails.append(primary_email)

        # Non primary identity profiles
        for idp in self.idp_profiles.filter(primary=False):
            entry = {
                'value': idp.email,
                'verified': True,
                'primary': False,
                'name': '{0}'.format(idp.get_type_display())
            }
            emails.append(entry)

        return emails

    def get_cis_uris(self):
        """Prepares the entry for URIs in the CIS format."""
        accounts = []
        for account in self.externalaccount_set.exclude(
                type=ExternalAccount.TYPE_EMAIL):
            value = account.get_identifier_url()
            account_type = ExternalAccount.ACCOUNT_TYPES[account.type]
            if value:
                entry = {
                    'value':
                    value,
                    'primary':
                    False,
                    'verified':
                    False,
                    'name':
                    'mozillians-{}-{}'.format(account_type['name'], account.pk)
                }
                accounts.append(entry)

        return accounts

    def get_cis_groups(self, idp):
        """Prepares the entry for profile groups in the CIS format."""

        # Update strategy: send groups for higher MFA idp
        # Wipe groups from the rest
        idps = list(self.idp_profiles.all().values_list('type', flat=True))

        # if the current idp does not match
        # the greatest number in the list, wipe the groups
        if not idps or idp.type != max(idps) or not idp.is_mfa():
            return []

        memberships = GroupMembership.objects.filter(
            userprofile=self,
            status=GroupMembership.MEMBER,
            group__is_access_group=True)
        groups = ['mozilliansorg_{}'.format(m.group.url) for m in memberships]
        return groups

    def get_cis_tags(self):
        """Prepares the entry for profile tags in the CIS format."""
        memberships = GroupMembership.objects.filter(
            userprofile=self,
            status=GroupMembership.MEMBER).exclude(group__is_access_group=True)

        tags = [m.group.url for m in memberships]
        return tags

    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', True)

        super(UserProfile, self).save(*args, **kwargs)
        # Auto_vouch follows the first save, because you can't
        # create foreign keys without a database id.

        if self.is_complete:
            send_userprofile_to_cis.delay(self.pk)

        if autovouch:
            self.auto_vouch()