2010-01-05 00:41:32 +03:00
|
|
|
from datetime import datetime
|
2010-01-13 04:22:26 +03:00
|
|
|
import hashlib
|
|
|
|
import random
|
2010-02-12 21:46:44 +03:00
|
|
|
import re
|
2010-01-13 04:22:26 +03:00
|
|
|
import string
|
2010-03-15 21:48:35 +03:00
|
|
|
import time
|
2010-01-05 00:41:32 +03:00
|
|
|
|
2010-02-12 21:46:44 +03:00
|
|
|
from django.conf import settings
|
2010-01-18 13:51:34 +03:00
|
|
|
from django.contrib.auth.models import User as DjangoUser
|
2010-03-29 19:09:52 +04:00
|
|
|
from django.core.mail import send_mail
|
2010-02-02 20:03:47 +03:00
|
|
|
from django.db import models
|
2010-03-29 19:09:52 +04:00
|
|
|
from django.template import Context, loader
|
2010-08-10 10:21:04 +04:00
|
|
|
from django.utils.encoding import smart_unicode
|
2009-10-23 02:37:15 +04:00
|
|
|
|
2010-08-05 11:37:22 +04:00
|
|
|
import caching.base as caching
|
2010-05-20 01:05:20 +04:00
|
|
|
import commonware.log
|
|
|
|
from tower import ugettext as _
|
|
|
|
|
2010-02-22 10:57:38 +03:00
|
|
|
import amo
|
2010-01-29 04:59:26 +03:00
|
|
|
import amo.models
|
2010-02-24 02:26:11 +03:00
|
|
|
from amo.urlresolvers import reverse
|
2010-02-23 22:47:16 +03:00
|
|
|
from translations.fields import PurifiedField
|
2009-10-23 02:37:15 +04:00
|
|
|
|
2010-05-20 01:05:20 +04:00
|
|
|
log = commonware.log.getLogger('z.users')
|
2010-03-12 09:56:44 +03:00
|
|
|
|
2009-10-23 02:37:15 +04:00
|
|
|
|
2010-01-13 04:22:26 +03:00
|
|
|
def get_hexdigest(algorithm, salt, raw_password):
|
|
|
|
return hashlib.new(algorithm, salt + raw_password).hexdigest()
|
|
|
|
|
|
|
|
|
|
|
|
def rand_string(length):
|
|
|
|
return ''.join(random.choice(string.letters) for i in xrange(length))
|
|
|
|
|
|
|
|
|
|
|
|
def create_password(algorithm, raw_password):
|
|
|
|
salt = get_hexdigest(algorithm, rand_string(12), rand_string(12))[:64]
|
|
|
|
hsh = get_hexdigest(algorithm, salt, raw_password)
|
|
|
|
return '$'.join([algorithm, salt, hsh])
|
|
|
|
|
|
|
|
|
2010-08-11 03:33:05 +04:00
|
|
|
class UserManager(amo.models.ManagerBase):
|
|
|
|
|
|
|
|
def request_user(self):
|
2010-08-16 21:46:45 +04:00
|
|
|
return (self.extra(select={'request': 1})
|
|
|
|
.transform(UserProfile.request_user_transformer))
|
2010-08-11 03:33:05 +04:00
|
|
|
|
|
|
|
|
2010-01-29 04:59:26 +03:00
|
|
|
class UserProfile(amo.models.ModelBase):
|
2009-10-23 02:37:15 +04:00
|
|
|
|
2010-03-26 03:41:29 +03:00
|
|
|
nickname = models.CharField(max_length=255, unique=True, default='',
|
|
|
|
null=True, blank=True)
|
|
|
|
firstname = models.CharField(max_length=255, default='', blank=True)
|
|
|
|
lastname = models.CharField(max_length=255, default='', blank=True)
|
2010-01-05 00:41:32 +03:00
|
|
|
password = models.CharField(max_length=255, default='')
|
2009-12-14 23:34:11 +03:00
|
|
|
email = models.EmailField(unique=True)
|
2010-01-05 00:41:32 +03:00
|
|
|
|
2010-06-24 01:20:46 +04:00
|
|
|
averagerating = models.CharField(max_length=255, blank=True, null=True)
|
2010-02-23 22:47:16 +03:00
|
|
|
bio = PurifiedField()
|
2010-02-09 04:02:15 +03:00
|
|
|
confirmationcode = models.CharField(max_length=255, default='',
|
|
|
|
blank=True)
|
2010-04-02 03:16:59 +04:00
|
|
|
deleted = models.BooleanField(default=False)
|
2010-01-05 00:41:32 +03:00
|
|
|
display_collections = models.BooleanField(default=False)
|
|
|
|
display_collections_fav = models.BooleanField(default=False)
|
2009-12-14 23:34:11 +03:00
|
|
|
emailhidden = models.BooleanField(default=False)
|
2010-04-02 03:16:59 +04:00
|
|
|
homepage = models.CharField(max_length=255, blank=True, default='')
|
|
|
|
location = models.CharField(max_length=255, blank=True, default='')
|
2010-06-24 01:20:46 +04:00
|
|
|
notes = models.TextField(blank=True, null=True)
|
2010-01-05 00:41:32 +03:00
|
|
|
notifycompat = models.BooleanField(default=True)
|
|
|
|
notifyevents = models.BooleanField(default=True)
|
2010-04-02 03:16:59 +04:00
|
|
|
occupation = models.CharField(max_length=255, default='', blank=True)
|
2010-06-10 06:23:22 +04:00
|
|
|
# This is essentially a "has_picture" flag right now
|
2010-02-09 04:02:15 +03:00
|
|
|
picture_type = models.CharField(max_length=75, default='', blank=True)
|
|
|
|
resetcode = models.CharField(max_length=255, default='', blank=True)
|
2010-06-11 07:00:56 +04:00
|
|
|
resetcode_expires = models.DateTimeField(default=datetime.now, null=True,
|
2010-02-09 04:02:15 +03:00
|
|
|
blank=True)
|
2009-12-14 23:34:11 +03:00
|
|
|
sandboxshown = models.BooleanField(default=False)
|
2010-01-05 00:41:32 +03:00
|
|
|
|
2010-02-09 04:02:15 +03:00
|
|
|
user = models.ForeignKey(DjangoUser, null=True, editable=False, blank=True)
|
2009-10-23 02:37:15 +04:00
|
|
|
|
2010-08-11 03:33:05 +04:00
|
|
|
objects = UserManager()
|
|
|
|
|
2009-10-23 02:37:15 +04:00
|
|
|
class Meta:
|
|
|
|
db_table = 'users'
|
|
|
|
|
2010-01-05 00:41:32 +03:00
|
|
|
def __unicode__(self):
|
|
|
|
return '%s: %s' % (self.id, self.display_name)
|
|
|
|
|
2010-02-26 21:41:49 +03:00
|
|
|
def get_url_path(self):
|
2010-02-02 20:03:47 +03:00
|
|
|
return reverse('users.profile', args=[self.id])
|
2009-10-23 02:37:15 +04:00
|
|
|
|
2010-08-13 20:24:36 +04:00
|
|
|
def flush_urls(self):
|
|
|
|
urls = ['*/user/%d/' % self.id]
|
|
|
|
|
|
|
|
return urls
|
|
|
|
|
2010-02-12 21:46:44 +03:00
|
|
|
@amo.cached_property
|
|
|
|
def addons_listed(self):
|
2010-06-22 21:51:29 +04:00
|
|
|
"""Public add-ons this user is listed as author of."""
|
|
|
|
return self.addons.valid().filter(addonuser__listed=True).distinct()
|
2010-02-12 21:46:44 +03:00
|
|
|
|
2010-03-26 03:41:29 +03:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Can be used while we're transitioning from separate first/last names
|
|
|
|
to a single field. Bug 546818#6"""
|
2010-04-06 08:17:31 +04:00
|
|
|
return (u'%s %s' % (self.firstname, self.lastname)).strip()
|
2010-03-26 03:41:29 +03:00
|
|
|
|
2010-02-12 21:46:44 +03:00
|
|
|
@property
|
|
|
|
def picture_url(self):
|
|
|
|
split_id = re.match(r'((\d*?)(\d{0,3}?))\d{1,3}$', str(self.id))
|
2010-03-15 21:48:35 +03:00
|
|
|
if not self.picture_type:
|
|
|
|
return settings.MEDIA_URL + '/img/zamboni/anon_user.png'
|
|
|
|
else:
|
|
|
|
return settings.USER_PIC_URL % (
|
|
|
|
split_id.group(2) or 0, split_id.group(1) or 0, self.id,
|
|
|
|
int(time.mktime(self.modified.timetuple())))
|
2010-02-12 21:46:44 +03:00
|
|
|
|
|
|
|
@amo.cached_property
|
|
|
|
def is_developer(self):
|
2010-02-23 22:47:16 +03:00
|
|
|
return bool(self.addons.filter(authors=self,
|
|
|
|
addonuser__listed=True)[:1])
|
2010-02-12 21:46:44 +03:00
|
|
|
|
2009-10-23 02:37:15 +04:00
|
|
|
@property
|
|
|
|
def display_name(self):
|
|
|
|
if not self.nickname:
|
|
|
|
return '%s %s' % (self.firstname, self.lastname)
|
|
|
|
else:
|
|
|
|
return self.nickname
|
2009-12-14 23:34:11 +03:00
|
|
|
|
|
|
|
@property
|
|
|
|
def welcome_name(self):
|
|
|
|
if self.firstname:
|
|
|
|
return self.firstname
|
|
|
|
elif self.nickname:
|
|
|
|
return self.nickname
|
|
|
|
elif self.lastname:
|
|
|
|
return self.lastname
|
|
|
|
|
|
|
|
return ''
|
|
|
|
|
2010-03-31 11:59:08 +04:00
|
|
|
@amo.cached_property
|
|
|
|
def reviews(self):
|
|
|
|
"""All reviews that are not dev replies."""
|
|
|
|
return self._reviews_all.filter(reply_to=None)
|
|
|
|
|
2010-03-26 03:41:29 +03:00
|
|
|
def anonymize(self):
|
|
|
|
log.info("User (%s: <%s>) is being anonymized." % (self, self.email))
|
|
|
|
self.email = ""
|
|
|
|
self.password = "sha512$Anonymous$Password"
|
|
|
|
self.firstname = ""
|
|
|
|
self.lastname = ""
|
2010-04-02 03:16:59 +04:00
|
|
|
self.nickname = None
|
2010-03-26 03:41:29 +03:00
|
|
|
self.homepage = ""
|
|
|
|
self.deleted = True
|
|
|
|
self.picture_type = ""
|
|
|
|
self.save()
|
|
|
|
|
2010-01-15 04:04:08 +03:00
|
|
|
def save(self, force_insert=False, force_update=False, using=None):
|
2009-12-14 23:34:11 +03:00
|
|
|
# we have to fix stupid things that we defined poorly in remora
|
|
|
|
if self.resetcode_expires is None:
|
|
|
|
self.resetcode_expires = datetime.now()
|
|
|
|
|
2010-01-15 04:04:08 +03:00
|
|
|
super(UserProfile, self).save(force_insert, force_update, using)
|
2010-01-13 04:22:26 +03:00
|
|
|
|
|
|
|
def check_password(self, raw_password):
|
|
|
|
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('$')
|
|
|
|
return hsh == get_hexdigest(algo, salt, raw_password)
|
|
|
|
|
|
|
|
def set_password(self, raw_password, algorithm='sha512'):
|
|
|
|
self.password = create_password(algorithm, raw_password)
|
2010-01-13 04:26:20 +03:00
|
|
|
|
2010-03-29 19:09:52 +04:00
|
|
|
def email_confirmation_code(self):
|
|
|
|
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
|
2010-04-01 07:49:13 +04:00
|
|
|
t = loader.get_template('users/email/confirm.ltxt')
|
2010-03-29 19:09:52 +04:00
|
|
|
c = {'domain': domain, 'url': url, }
|
|
|
|
send_mail(_("Please confirm your email address"),
|
|
|
|
t.render(Context(c)), None, [self.email])
|
|
|
|
|
2010-01-13 04:26:20 +03:00
|
|
|
def create_django_user(self):
|
|
|
|
"""Make a django.contrib.auth.User for this UserProfile."""
|
|
|
|
# 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.
|
2010-01-18 22:45:23 +03:00
|
|
|
self.user = DjangoUser(id=self.pk)
|
2010-01-13 04:26:20 +03:00
|
|
|
self.user.first_name = self.firstname
|
|
|
|
self.user.last_name = self.lastname
|
2010-01-15 02:26:46 +03:00
|
|
|
self.user.username = self.email
|
2010-01-13 04:26:20 +03:00
|
|
|
self.user.email = self.email
|
|
|
|
self.user.password = self.password
|
|
|
|
self.user.date_joined = self.created
|
|
|
|
|
2010-05-18 15:29:16 +04:00
|
|
|
if self.groups.filter(rules='*:*').count():
|
2010-01-13 04:26:20 +03:00
|
|
|
self.user.is_superuser = self.user.is_staff = True
|
|
|
|
|
|
|
|
self.user.save()
|
|
|
|
self.save()
|
|
|
|
return self.user
|
2010-07-13 07:46:23 +04:00
|
|
|
|
2010-08-10 05:37:34 +04:00
|
|
|
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, _ = Collection.objects.get_or_create(
|
|
|
|
author=self, type=type_, defaults=defaults)
|
|
|
|
return c
|
|
|
|
|
2010-08-11 03:33:05 +04:00
|
|
|
@staticmethod
|
|
|
|
def request_user_transformer(users):
|
|
|
|
"""Adds extra goodies to a UserProfile (meant for request.amo_user)."""
|
|
|
|
# We don't want to cache these things on every UserProfile; they're
|
|
|
|
# only used by a user attached to a request.
|
|
|
|
from bandwagon.models import CollectionAddon
|
|
|
|
user = users[0]
|
|
|
|
qs = CollectionAddon.objects.filter(
|
|
|
|
collection__author=user, collection__type=amo.COLLECTION_MOBILE)
|
|
|
|
user.mobile_addons = qs.values_list('addon', flat=True)
|
|
|
|
|
|
|
|
|
2010-07-13 07:46:23 +04:00
|
|
|
class BlacklistedNickname(amo.models.ModelBase):
|
|
|
|
"""Blacklisted user nicknames."""
|
|
|
|
nickname = models.CharField(max_length=255, unique=True, default='')
|
|
|
|
|
|
|
|
def __unicode__(self):
|
|
|
|
return self.nickname
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def blocked(cls, nick):
|
2010-08-05 11:37:22 +04:00
|
|
|
"""Check to see if a nickname is in the (cached) blacklist."""
|
2010-08-10 10:21:04 +04:00
|
|
|
nick = smart_unicode(nick).lower()
|
2010-08-05 11:37:22 +04:00
|
|
|
qs = cls.objects.all()
|
|
|
|
f = lambda: dict(qs.values_list('nickname', 'id'))
|
|
|
|
blacklist = caching.cached_with(qs, f, 'blocked')
|
|
|
|
return nick in blacklist
|
2010-08-05 04:29:03 +04:00
|
|
|
|
|
|
|
|
|
|
|
class PersonaAuthor(unicode):
|
|
|
|
"""Stub user until the persona authors get imported."""
|
|
|
|
|
2010-08-17 00:19:55 +04:00
|
|
|
@property
|
|
|
|
def id(self):
|
|
|
|
"""I don't want to change code depending on PersonaAuthor.id, so I'm
|
|
|
|
just hardcoding 0. The only code using this is flush_urls."""
|
|
|
|
return 0
|
|
|
|
|
2010-08-05 04:29:03 +04:00
|
|
|
@property
|
|
|
|
def display_name(self):
|
|
|
|
return self
|