Esempio n. 1
0
class UserProfile(amo.models.OnChangeMixin, amo.models.ModelBase):
    username = models.CharField(max_length=255, default='', unique=True)
    display_name = models.CharField(max_length=255,
                                    default='',
                                    null=True,
                                    blank=True)

    password = models.CharField(max_length=255, default='')
    email = models.EmailField(unique=True, null=True)

    averagerating = models.CharField(max_length=255, blank=True, null=True)
    bio = NoLinksField(short=False)
    confirmationcode = models.CharField(max_length=255, default='', blank=True)
    deleted = models.BooleanField(default=False)
    display_collections = models.BooleanField(default=False)
    display_collections_fav = models.BooleanField(default=False)
    emailhidden = models.BooleanField(default=True)
    homepage = models.URLField(max_length=255, blank=True, default='')
    location = models.CharField(max_length=255, blank=True, default='')
    notes = models.TextField(blank=True, null=True)
    notifycompat = models.BooleanField(default=True)
    notifyevents = models.BooleanField(default=True)
    occupation = models.CharField(max_length=255, default='', blank=True)
    # This is essentially a "has_picture" flag right now
    picture_type = models.CharField(max_length=75, default='', blank=True)
    resetcode = models.CharField(max_length=255, default='', blank=True)
    resetcode_expires = models.DateTimeField(default=datetime.now,
                                             null=True,
                                             blank=True)
    read_dev_agreement = models.DateTimeField(null=True, blank=True)

    last_login_ip = models.CharField(default='', max_length=45, editable=False)
    last_login_attempt = models.DateTimeField(null=True, editable=False)
    last_login_attempt_ip = models.CharField(default='',
                                             max_length=45,
                                             editable=False)
    failed_login_attempts = models.PositiveIntegerField(default=0,
                                                        editable=False)
    source = models.PositiveIntegerField(default=amo.LOGIN_SOURCE_UNKNOWN,
                                         editable=False,
                                         db_index=True)
    user = models.ForeignKey(DjangoUser, null=True, editable=False, blank=True)
    is_verified = models.BooleanField(default=True)
    region = models.CharField(max_length=11,
                              null=True,
                              blank=True,
                              editable=False)
    lang = models.CharField(max_length=5,
                            null=True,
                            blank=True,
                            editable=False)

    class Meta:
        db_table = 'users'

    def __init__(self, *args, **kw):
        super(UserProfile, self).__init__(*args, **kw)
        if self.username:
            self.username = smart_unicode(self.username)

    def __unicode__(self):
        return u'%s: %s' % (self.id, self.display_name or self.username)

    def is_anonymous(self):
        return False

    def get_user_url(self, name='profile', src=None, args=None):
        """
        We use <username> as the slug, unless it contains gross
        characters - in which case use <id> as the slug.
        """
        # TODO: Remove this ASAP (bug 880767).
        if name == 'profile':
            return '#'
        from amo.utils import urlparams
        chars = '/<>"\''
        slug = self.username
        if not self.username or any(x in chars for x in self.username):
            slug = self.id
        args = args or []
        url = reverse('users.%s' % name, args=[slug] + args)
        return urlparams(url, src=src)

    def get_url_path(self, src=None):
        return self.get_user_url('profile', src=src)

    def flush_urls(self):
        urls = [
            '*/user/%d/' % self.id,
            self.picture_url,
        ]

        return urls

    @amo.cached_property
    def addons_listed(self):
        """Public add-ons this user is listed as author of."""
        return self.addons.reviewed().exclude(type=amo.ADDON_WEBAPP).filter(
            addonuser__user=self, addonuser__listed=True)

    @property
    def num_addons_listed(self):
        """Number of public add-ons this user is listed as author of."""
        return self.addons.reviewed().exclude(type=amo.ADDON_WEBAPP).filter(
            addonuser__user=self, addonuser__listed=True).count()

    @amo.cached_property
    def apps_listed(self):
        """Public apps this user is listed as author of."""
        return self.addons.reviewed().filter(type=amo.ADDON_WEBAPP,
                                             addonuser__user=self,
                                             addonuser__listed=True)

    def my_addons(self, n=8):
        """Returns n addons (anything not a webapp)"""
        qs = self.addons.exclude(type=amo.ADDON_WEBAPP)
        qs = order_by_translation(qs, 'name')
        return qs[:n]

    def my_apps(self, n=8):
        """Returns n apps"""
        qs = self.addons.filter(type=amo.ADDON_WEBAPP)
        qs = order_by_translation(qs, 'name')
        return qs[:n]

    @property
    def picture_dir(self):
        split_id = re.match(r'((\d*?)(\d{0,3}?))\d{1,3}$', str(self.id))
        return os.path.join(settings.USERPICS_PATH,
                            split_id.group(2) or '0',
                            split_id.group(1) or '0')

    @property
    def picture_path(self):
        return os.path.join(self.picture_dir, str(self.id) + '.png')

    @property
    def picture_url(self):
        if not self.picture_type:
            return settings.MEDIA_URL + '/img/zamboni/anon_user.png'
        else:
            split_id = re.match(r'((\d*?)(\d{0,3}?))\d{1,3}$', str(self.id))
            return settings.USERPICS_URL % (
                split_id.group(2) or 0, split_id.group(1)
                or 0, self.id, int(time.mktime(self.modified.timetuple())))

    @amo.cached_property
    def is_developer(self):
        return self.addonuser_set.exists()

    @amo.cached_property
    def is_addon_developer(self):
        return self.addonuser_set.exclude(
            addon__type=amo.ADDON_PERSONA).exists()

    @amo.cached_property
    def is_app_developer(self):
        return self.addonuser_set.filter(addon__type=amo.ADDON_WEBAPP).exists()

    @amo.cached_property
    def needs_tougher_password(user):
        if user.source in amo.LOGIN_SOURCE_BROWSERIDS:
            return False
        from access import acl
        return (acl.action_allowed_user(user, 'Admin', '%')
                or acl.action_allowed_user(user, 'Addons', 'Edit')
                or acl.action_allowed_user(user, 'Addons', 'Review')
                or acl.action_allowed_user(user, 'Apps', 'Review')
                or acl.action_allowed_user(user, 'Users', 'Edit'))

    @property
    def name(self):
        return smart_unicode(self.display_name or self.username)

    welcome_name = name

    @property
    def last_login(self):
        """Make UserProfile look more like auth.User."""
        # Django expects this to be non-null, so fake a login attempt.
        if not self.last_login_attempt:
            self.update(last_login_attempt=datetime.now())
        return self.last_login_attempt

    @amo.cached_property
    def reviews(self):
        """All reviews that are not dev replies."""
        qs = self._reviews_all.filter(reply_to=None)
        # Force the query to occur immediately. Several
        # reviews-related tests hang if this isn't done.
        return qs

    def anonymize(self):
        log.info(u"User (%s: <%s>) is being anonymized." % (self, self.email))
        self.email = None
        self.password = "******"
        self.username = "******" % self.id  # Can't be null
        self.display_name = None
        self.homepage = ""
        self.deleted = True
        self.picture_type = ""
        self.save()

    @transaction.commit_on_success
    def restrict(self):
        from amo.utils import send_mail
        log.info(u'User (%s: <%s>) is being restricted and '
                 'its user-generated content removed.' % (self, self.email))
        g = Group.objects.get(rules='Restricted:UGC')
        GroupUser.objects.create(user=self, group=g)
        self.reviews.all().delete()
        self.collections.all().delete()

        t = loader.get_template('users/email/restricted.ltxt')
        send_mail(_('Your account has been restricted'),
                  t.render(Context({})),
                  None, [self.email],
                  use_blacklist=False,
                  real_email=True)

    def unrestrict(self):
        log.info(u'User (%s: <%s>) is being unrestricted.' %
                 (self, self.email))
        GroupUser.objects.filter(user=self,
                                 group__rules='Restricted:UGC').delete()

    def generate_confirmationcode(self):
        if not self.confirmationcode:
            self.confirmationcode = ''.join(
                random.sample(string.letters + string.digits, 60))
        return self.confirmationcode

    def save(self, force_insert=False, force_update=False, using=None):
        # we have to fix stupid things that we defined poorly in remora
        if not self.resetcode_expires:
            self.resetcode_expires = datetime.now()

        delete_user = None
        if self.deleted and self.user:
            delete_user = self.user
            self.user = None
            # Delete user after saving this profile.

        super(UserProfile, self).save(force_insert, force_update, using)

        if self.deleted and delete_user:
            delete_user.delete()

    def check_password(self, raw_password):
        # BrowserID does not store a password.
        if self.source in amo.LOGIN_SOURCE_BROWSERIDS:
            return True

        if '$' not in self.password:
            valid = (get_hexdigest('md5', '', raw_password) == self.password)
            if valid:
                # Upgrade an old password.
                self.set_password(raw_password)
                self.save()
            return valid

        algo, salt, hsh = self.password.split('$')
        #Complication due to getpersonas account migration; we don't
        #know if passwords were utf-8 or latin-1 when hashed. If you
        #can prove that they are one or the other, you can delete one
        #of these branches.
        if '+base64' in algo and isinstance(raw_password, unicode):
            if hsh == get_hexdigest(algo, salt, raw_password.encode('utf-8')):
                return True
            else:
                try:
                    return hsh == get_hexdigest(algo, salt,
                                                raw_password.encode('latin1'))
                except UnicodeEncodeError:
                    return False
        else:
            return hsh == get_hexdigest(algo, salt, raw_password)

    def set_password(self, raw_password, algorithm='sha512'):
        self.password = create_password(algorithm, raw_password)
        # Can't do CEF logging here because we don't have a request object.

    def email_confirmation_code(self):
        from amo.utils import send_mail
        log.debug("Sending account confirmation code for user (%s)", self)

        url = "%s%s" % (settings.SITE_URL,
                        reverse('users.confirm',
                                args=[self.id, self.confirmationcode]))
        domain = settings.DOMAIN
        t = loader.get_template('users/email/confirm.ltxt')
        c = {
            'domain': domain,
            'url': url,
        }
        send_mail(_("Please confirm your email address"),
                  t.render(Context(c)),
                  None, [self.email],
                  use_blacklist=False,
                  real_email=True)

    def log_login_attempt(self, successful):
        """Log a user's login attempt"""
        self.last_login_attempt = datetime.now()
        self.last_login_attempt_ip = commonware.log.get_remote_addr()

        if successful:
            log.debug(u"User (%s) logged in successfully" % self)
            self.failed_login_attempts = 0
            self.last_login_ip = commonware.log.get_remote_addr()
        else:
            log.debug(u"User (%s) failed to log in" % self)
            if self.failed_login_attempts < 16777216:
                self.failed_login_attempts += 1

        self.save()

    def create_django_user(self, **kw):
        """Make a django.contrib.auth.User for this UserProfile."""
        # Due to situations like bug 905984 and similar, a django user
        # for this email may already exist. Let's try to find it first
        # before creating a new one.
        try:
            self.user = DjangoUser.objects.get(email=self.email)
            for k, v in kw.iteritems():
                setattr(self.user, k, v)
            self.save()
            return self.user
        except DjangoUser.DoesNotExist:
            pass

        # Reusing the id will make our life easier, because we can use the
        # OneToOneField as pk for Profile linked back to the auth.user
        # in the future.
        self.user = DjangoUser(id=self.pk)
        self.user.first_name = ''
        self.user.last_name = ''
        self.user.username = '******' % self.pk
        self.user.email = self.email
        self.user.password = self.password
        self.user.date_joined = self.created

        for k, v in kw.iteritems():
            setattr(self.user, k, v)

        if self.groups.filter(rules='*:*').count():
            self.user.is_superuser = self.user.is_staff = True

        self.user.save()
        self.save()
        return self.user

    def mobile_collection(self):
        return self.special_collection(amo.COLLECTION_MOBILE,
                                       defaults={
                                           'slug': 'mobile',
                                           'listed': False,
                                           'name': _('My Mobile Add-ons')
                                       })

    def favorites_collection(self):
        return self.special_collection(amo.COLLECTION_FAVORITES,
                                       defaults={
                                           'slug': 'favorites',
                                           'listed': False,
                                           'name': _('My Favorite Add-ons')
                                       })

    def special_collection(self, type_, defaults):
        from bandwagon.models import Collection
        c, new = Collection.objects.get_or_create(author=self,
                                                  type=type_,
                                                  defaults=defaults)
        if new:
            # Do an extra query to make sure this gets transformed.
            c = Collection.objects.using('default').get(id=c.id)
        return c

    def purchase_ids(self):
        """
        I'm special casing this because we use purchase_ids a lot in the site
        and we are not caching empty querysets in cache-machine.
        That means that when the site is first launched we are having a
        lot of empty queries hit.

        We can probably do this in smarter fashion by making cache-machine
        cache empty queries on an as need basis.
        """
        # Circular import
        from market.models import AddonPurchase

        @memoize(prefix='users:purchase-ids')
        def ids(pk):
            return (AddonPurchase.objects.filter(user=pk).values_list(
                'addon_id',
                flat=True).filter(type=amo.CONTRIB_PURCHASE).order_by('pk'))

        return ids(self.pk)

    @contextmanager
    def activate_lang(self):
        """
        Activate the language for the user. If none is set will go to the site
        default which is en-US.
        """
        lang = self.lang if self.lang else settings.LANGUAGE_CODE
        old = translation.get_language()
        tower.activate(lang)
        yield
        tower.activate(old)

    def remove_locale(self, locale):
        """Remove the given locale for the user."""
        Translation.objects.remove_for(self, locale)
Esempio n. 2
0
class UserProfile(amo.models.OnChangeMixin, amo.models.ModelBase,
                  AbstractBaseUser):
    objects = UserManager()
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']
    username = models.CharField(max_length=255, default='', unique=True)
    display_name = models.CharField(max_length=255, default='', null=True,
                                    blank=True)

    email = models.EmailField(unique=True, null=True)

    averagerating = models.CharField(max_length=255, blank=True, null=True)
    bio = NoLinksField(short=False)
    confirmationcode = models.CharField(max_length=255, default='',
                                        blank=True)
    deleted = models.BooleanField(default=False)
    display_collections = models.BooleanField(default=False)
    display_collections_fav = models.BooleanField(default=False)
    homepage = models.URLField(max_length=255, blank=True, default='')
    location = models.CharField(max_length=255, blank=True, default='')
    notes = models.TextField(blank=True, null=True)
    notifycompat = models.BooleanField(default=True)
    notifyevents = models.BooleanField(default=True)
    occupation = models.CharField(max_length=255, default='', blank=True)
    # This is essentially a "has_picture" flag right now
    picture_type = models.CharField(max_length=75, default='', blank=True)
    read_dev_agreement = models.DateTimeField(null=True, blank=True)

    last_login_ip = models.CharField(default='', max_length=45, editable=False)
    last_login_attempt = models.DateTimeField(null=True, editable=False)
    last_login_attempt_ip = models.CharField(default='', max_length=45,
                                             editable=False)
    failed_login_attempts = models.PositiveIntegerField(default=0,
                                                        editable=False)

    is_verified = models.BooleanField(default=True)
    region = models.CharField(max_length=11, null=True, blank=True,
                              editable=False)
    lang = models.CharField(max_length=5, null=True, blank=True,
                            default=settings.LANGUAGE_CODE)

    t_shirt_requested = models.DateTimeField(blank=True, null=True,
                                             default=None, editable=False)
    fxa_id = models.CharField(blank=True, null=True, max_length=128)

    class Meta:
        db_table = 'users'

    def __init__(self, *args, **kw):
        super(UserProfile, self).__init__(*args, **kw)
        if self.username:
            self.username = smart_unicode(self.username)

    def __unicode__(self):
        return u'%s: %s' % (self.id, self.display_name or self.username)

    @property
    def is_superuser(self):
        return self.groups.filter(rules='*:*').exists()

    @property
    def is_staff(self):
        from access import acl
        return acl.action_allowed_user(self, 'Admin', '%')

    def has_perm(self, perm, obj=None):
        return self.is_superuser

    def has_module_perms(self, app_label):
        return self.is_superuser

    backend = 'users.backends.AmoUserBackend'

    def is_anonymous(self):
        return False

    def get_user_url(self, name='profile', src=None, args=None):
        """
        We use <username> as the slug, unless it contains gross
        characters - in which case use <id> as the slug.
        """
        from amo.utils import urlparams
        chars = '/<>"\''
        slug = self.username
        if not self.username or any(x in chars for x in self.username):
            slug = self.id
        args = args or []
        url = reverse('users.%s' % name, args=[slug] + args)
        return urlparams(url, src=src)

    def get_url_path(self, src=None):
        return self.get_user_url('profile', src=src)

    def flush_urls(self):
        urls = ['*/user/%d/' % self.id,
                self.picture_url,
                ]

        return urls

    @amo.cached_property
    def addons_listed(self):
        """Public add-ons this user is listed as author of."""
        return self.addons.reviewed().filter(
            addonuser__user=self, addonuser__listed=True)

    @property
    def num_addons_listed(self):
        """Number of public add-ons this user is listed as author of."""
        return self.addons.reviewed().filter(
            addonuser__user=self, addonuser__listed=True).count()

    def my_addons(self, n=8, with_unlisted=False):
        """Returns n addons"""
        addons = self.addons
        if with_unlisted:
            addons = self.addons.model.with_unlisted.filter(authors=self)
        qs = order_by_translation(addons, 'name')
        return qs[:n]

    @property
    def picture_dir(self):
        from amo.helpers import user_media_path
        split_id = re.match(r'((\d*?)(\d{0,3}?))\d{1,3}$', str(self.id))
        return os.path.join(user_media_path('userpics'),
                            split_id.group(2) or '0',
                            split_id.group(1) or '0')

    @property
    def picture_path(self):
        return os.path.join(self.picture_dir, str(self.id) + '.png')

    @property
    def picture_url(self):
        from amo.helpers import user_media_url
        if not self.picture_type:
            return settings.STATIC_URL + '/img/zamboni/anon_user.png'
        else:
            split_id = re.match(r'((\d*?)(\d{0,3}?))\d{1,3}$', str(self.id))
            modified = int(time.mktime(self.modified.timetuple()))
            path = "/".join([
                split_id.group(2) or '0',
                split_id.group(1) or '0',
                "%s.png?modified=%s" % (self.id, modified)
            ])
            return user_media_url('userpics') + path

    @amo.cached_property
    def is_developer(self):
        return self.addonuser_set.exists()

    @amo.cached_property
    def is_addon_developer(self):
        return self.addonuser_set.exclude(
            addon__type=amo.ADDON_PERSONA).exists()

    @amo.cached_property
    def is_artist(self):
        """Is this user a Personas Artist?"""
        return self.addonuser_set.filter(
            addon__type=amo.ADDON_PERSONA).exists()

    @amo.cached_property
    def needs_tougher_password(user):
        from access import acl
        return (acl.action_allowed_user(user, 'Admin', '%') or
                acl.action_allowed_user(user, 'Addons', 'Edit') or
                acl.action_allowed_user(user, 'Addons', 'Review') or
                acl.action_allowed_user(user, 'Apps', 'Review') or
                acl.action_allowed_user(user, 'Personas', 'Review') or
                acl.action_allowed_user(user, 'Users', 'Edit'))

    @property
    def name(self):
        return smart_unicode(self.display_name or self.username)

    welcome_name = name

    @amo.cached_property
    def reviews(self):
        """All reviews that are not dev replies."""
        qs = self._reviews_all.filter(reply_to=None)
        # Force the query to occur immediately. Several
        # reviews-related tests hang if this isn't done.
        return qs

    def anonymize(self):
        log.info(u"User (%s: <%s>) is being anonymized." % (self, self.email))
        self.email = None
        self.password = "******"
        self.fxa_id = None
        self.username = "******" % self.id  # Can't be null
        self.display_name = None
        self.homepage = ""
        self.deleted = True
        self.picture_type = ""
        self.save()

    @transaction.commit_on_success
    def restrict(self):
        from amo.utils import send_mail
        log.info(u'User (%s: <%s>) is being restricted and '
                 'its user-generated content removed.' % (self, self.email))
        g = Group.objects.get(rules='Restricted:UGC')
        GroupUser.objects.create(user=self, group=g)
        self.reviews.all().delete()
        self.collections.all().delete()

        t = loader.get_template('users/email/restricted.ltxt')
        send_mail(_('Your account has been restricted'),
                  t.render(Context({})), None, [self.email],
                  use_blacklist=False, real_email=True)

    def unrestrict(self):
        log.info(u'User (%s: <%s>) is being unrestricted.' % (self,
                                                              self.email))
        GroupUser.objects.filter(user=self,
                                 group__rules='Restricted:UGC').delete()

    def generate_confirmationcode(self):
        if not self.confirmationcode:
            self.confirmationcode = ''.join(random.sample(string.letters +
                                                          string.digits, 60))
        return self.confirmationcode

    def set_unusable_password(self):
        self.password = ''

    def has_usable_password(self):
        """Override AbstractBaseUser.has_usable_password."""
        # We also override the check_password method, and don't rely on
        # settings.PASSWORD_HASHERS, and don't use "set_unusable_password", so
        # we want to bypass most of AbstractBaseUser.has_usable_password
        # checks.
        return bool(self.password)  # Not None and not empty.

    def check_password(self, raw_password):
        if not self.has_usable_password():
            return False

        if '$' not in self.password:
            valid = (get_hexdigest('md5', '', raw_password) == self.password)
            if valid:
                # Upgrade an old password.
                self.set_password(raw_password)
                self.save()
            return valid

        algo, salt, hsh = self.password.split('$')
        # Complication due to getpersonas account migration; we don't
        # know if passwords were utf-8 or latin-1 when hashed. If you
        # can prove that they are one or the other, you can delete one
        # of these branches.
        if '+base64' in algo and isinstance(raw_password, unicode):
            if hsh == get_hexdigest(algo, salt, raw_password.encode('utf-8')):
                return True
            else:
                try:
                    return hsh == get_hexdigest(algo, salt,
                                                raw_password.encode('latin1'))
                except UnicodeEncodeError:
                    return False
        else:
            return hsh == get_hexdigest(algo, salt, raw_password)

    def set_password(self, raw_password, algorithm='sha512'):
        self.password = create_password(algorithm, raw_password)
        # Can't do CEF logging here because we don't have a request object.

    def email_confirmation_code(self):
        from amo.utils import send_mail
        log.debug("Sending account confirmation code for user (%s)", self)

        url = "%s%s" % (settings.SITE_URL,
                        reverse('users.confirm',
                                args=[self.id, self.confirmationcode]))
        domain = settings.DOMAIN
        t = loader.get_template('users/email/confirm.ltxt')
        c = {'domain': domain, 'url': url, }
        send_mail(_("Please confirm your email address"),
                  t.render(Context(c)), None, [self.email],
                  use_blacklist=False, real_email=True)

    def log_login_attempt(self, successful):
        """Log a user's login attempt"""
        self.last_login_attempt = datetime.now()
        self.last_login_attempt_ip = commonware.log.get_remote_addr()

        if successful:
            log.debug(u"User (%s) logged in successfully" % self)
            self.failed_login_attempts = 0
            self.last_login_ip = commonware.log.get_remote_addr()
        else:
            log.debug(u"User (%s) failed to log in" % self)
            if self.failed_login_attempts < 16777216:
                self.failed_login_attempts += 1

        self.save(update_fields=['last_login_ip', 'last_login_attempt',
                                 'last_login_attempt_ip',
                                 'failed_login_attempts'])

    def mobile_collection(self):
        return self.special_collection(
            amo.COLLECTION_MOBILE,
            defaults={'slug': 'mobile', 'listed': False,
                      'name': _('My Mobile Add-ons')})

    def favorites_collection(self):
        return self.special_collection(
            amo.COLLECTION_FAVORITES,
            defaults={'slug': 'favorites', 'listed': False,
                      'name': _('My Favorite Add-ons')})

    def special_collection(self, type_, defaults):
        from bandwagon.models import Collection
        c, new = Collection.objects.get_or_create(
            author=self, type=type_, defaults=defaults)
        if new:
            # Do an extra query to make sure this gets transformed.
            c = Collection.objects.using('default').get(id=c.id)
        return c

    @contextmanager
    def activate_lang(self):
        """
        Activate the language for the user. If none is set will go to the site
        default which is en-US.
        """
        lang = self.lang if self.lang else settings.LANGUAGE_CODE
        old = translation.get_language()
        tower.activate(lang)
        yield
        tower.activate(old)

    def remove_locale(self, locale):
        """Remove the given locale for the user."""
        Translation.objects.remove_for(self, locale)

    @classmethod
    def get_fallback(cls):
        return cls._meta.get_field('lang')

    def addons_for_collection_type(self, type_):
        """Return the addons for the given special collection type."""
        from bandwagon.models import CollectionAddon
        qs = CollectionAddon.objects.filter(
            collection__author=self, collection__type=type_)
        return qs.values_list('addon', flat=True)

    @amo.cached_property
    def mobile_addons(self):
        return self.addons_for_collection_type(amo.COLLECTION_MOBILE)

    @amo.cached_property
    def favorite_addons(self):
        return self.addons_for_collection_type(amo.COLLECTION_FAVORITES)

    @amo.cached_property
    def watching(self):
        return self.collectionwatcher_set.values_list('collection', flat=True)
Esempio n. 3
0
class UserProfile(amo.models.OnChangeMixin, amo.models.ModelBase,
                  AbstractBaseUser):

    USERNAME_FIELD = 'username'
    username = models.CharField(max_length=255, default='', unique=True)
    display_name = models.CharField(max_length=255,
                                    default='',
                                    null=True,
                                    blank=True)

    email = models.EmailField(unique=True, null=True)

    averagerating = models.CharField(max_length=255, blank=True, null=True)
    bio = NoLinksField(short=False)
    confirmationcode = models.CharField(max_length=255, default='', blank=True)
    deleted = models.BooleanField(default=False)
    display_collections = models.BooleanField(default=False)
    display_collections_fav = models.BooleanField(default=False)
    emailhidden = models.BooleanField(default=True)
    homepage = models.URLField(max_length=255, blank=True, default='')
    location = models.CharField(max_length=255, blank=True, default='')
    notes = models.TextField(blank=True, null=True)
    notifycompat = models.BooleanField(default=True)
    notifyevents = models.BooleanField(default=True)
    occupation = models.CharField(max_length=255, default='', blank=True)
    # This is essentially a "has_picture" flag right now
    picture_type = models.CharField(max_length=75, default='', blank=True)
    resetcode = models.CharField(max_length=255, default='', blank=True)
    resetcode_expires = models.DateTimeField(default=datetime.now,
                                             null=True,
                                             blank=True)
    read_dev_agreement = models.DateTimeField(null=True, blank=True)

    last_login_ip = models.CharField(default='', max_length=45, editable=False)
    last_login_attempt = models.DateTimeField(null=True, editable=False)
    last_login_attempt_ip = models.CharField(default='',
                                             max_length=45,
                                             editable=False)
    failed_login_attempts = models.PositiveIntegerField(default=0,
                                                        editable=False)
    source = models.PositiveIntegerField(default=amo.LOGIN_SOURCE_UNKNOWN,
                                         editable=False,
                                         db_index=True)

    is_verified = models.BooleanField(default=True)
    region = models.CharField(max_length=11,
                              null=True,
                              blank=True,
                              editable=False)
    lang = models.CharField(max_length=5,
                            null=True,
                            blank=True,
                            editable=False)

    class Meta:
        db_table = 'users'

    def __init__(self, *args, **kw):
        super(UserProfile, self).__init__(*args, **kw)
        if self.username:
            self.username = smart_unicode(self.username)

    def __unicode__(self):
        return u'%s: %s' % (self.id, self.display_name or self.username)

    def save(self,
             force_insert=False,
             force_update=False,
             using=None,
             **kwargs):
        # we have to fix stupid things that we defined poorly in remora
        if not self.resetcode_expires:
            self.resetcode_expires = datetime.now()
        super(UserProfile, self).save(force_insert, force_update, using,
                                      **kwargs)

    @property
    def is_superuser(self):
        return self.groups.filter(rules='*:*').exists()

    @property
    def is_staff(self):
        from access import acl
        return acl.action_allowed_user(self, 'Admin', '%')

    def has_perm(self, perm, obj=None):
        return self.is_superuser

    def has_module_perms(self, app_label):
        return self.is_superuser

    def get_backend(self):
        return 'django_browserid.auth.BrowserIDBackend'

    def set_backend(self, val):
        pass

    backend = property(get_backend, set_backend)

    def is_anonymous(self):
        return False

    def get_url_path(self, src=None):
        # See: bug 880767.
        return '#'

    def my_apps(self, n=8):
        """Returns n apps"""
        qs = self.addons.filter(type=amo.ADDON_WEBAPP)
        qs = order_by_translation(qs, 'name')
        return qs[:n]

    @property
    def picture_url(self):
        if not self.picture_type:
            return settings.MEDIA_URL + '/img/zamboni/anon_user.png'
        else:
            split_id = re.match(r'((\d*?)(\d{0,3}?))\d{1,3}$', str(self.id))
            return settings.USERPICS_URL % (
                split_id.group(2) or 0, split_id.group(1)
                or 0, self.id, int(time.mktime(self.modified.timetuple())))

    @amo.cached_property
    def is_developer(self):
        return self.addonuser_set.exists()

    @property
    def name(self):
        return smart_unicode(self.display_name or self.username)

    @amo.cached_property
    def reviews(self):
        """All reviews that are not dev replies."""
        qs = self._reviews_all.filter(reply_to=None)
        # Force the query to occur immediately. Several
        # reviews-related tests hang if this isn't done.
        return qs

    def anonymize(self):
        log.info(u"User (%s: <%s>) is being anonymized." % (self, self.email))
        self.email = None
        self.password = "******"
        self.username = "******" % self.id  # Can't be null
        self.display_name = None
        self.homepage = ""
        self.deleted = True
        self.picture_type = ""
        self.save()

    def check_password(self, raw_password):
        # BrowserID does not store a password.
        return True

    def log_login_attempt(self, successful):
        """Log a user's login attempt"""
        self.last_login_attempt = datetime.now()
        self.last_login_attempt_ip = commonware.log.get_remote_addr()

        if successful:
            log.debug(u"User (%s) logged in successfully" % self)
            self.failed_login_attempts = 0
            self.last_login_ip = commonware.log.get_remote_addr()
        else:
            log.debug(u"User (%s) failed to log in" % self)
            if self.failed_login_attempts < 16777216:
                self.failed_login_attempts += 1

        self.save()

    def purchase_ids(self):
        """
        I'm special casing this because we use purchase_ids a lot in the site
        and we are not caching empty querysets in cache-machine.
        That means that when the site is first launched we are having a
        lot of empty queries hit.

        We can probably do this in smarter fashion by making cache-machine
        cache empty queries on an as need basis.
        """
        # Circular import
        from mkt.prices.models import AddonPurchase

        @memoize(prefix='users:purchase-ids')
        def ids(pk):
            return (AddonPurchase.objects.filter(user=pk).values_list(
                'addon_id',
                flat=True).filter(type=amo.CONTRIB_PURCHASE).order_by('pk'))

        return ids(self.pk)

    @contextmanager
    def activate_lang(self):
        """
        Activate the language for the user. If none is set will go to the site
        default which is en-US.
        """
        lang = self.lang if self.lang else settings.LANGUAGE_CODE
        old = translation.get_language()
        tower.activate(lang)
        yield
        tower.activate(old)