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