models.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. import hashlib
  2. import math
  3. from random import choice
  4. from django.conf import settings
  5. from django.contrib.auth.hashers import (
  6. check_password, make_password, is_password_usable, UNUSABLE_PASSWORD)
  7. from django.core.exceptions import ValidationError
  8. from django.core.mail import EmailMultiAlternatives
  9. from django.db import models, connection, transaction
  10. from django.template import RequestContext
  11. from django.utils import timezone as tz_util
  12. from django.utils.translation import ugettext_lazy as _
  13. from misago.acl.models import Role
  14. from misago.monitor.monitor import Monitor
  15. from misago.security import get_random_string
  16. from misago.settings.settings import Settings as DBSettings
  17. from misago.users.validators import validate_username, validate_password, validate_email
  18. from misago.utils import slugify
  19. from path import path
  20. class UserManager(models.Manager):
  21. """
  22. User Manager provides us with some additional methods for users
  23. """
  24. def get_blank_user(self):
  25. blank_user = User(
  26. join_date=tz_util.now(),
  27. join_ip='127.0.0.1'
  28. )
  29. return blank_user
  30. def resync_monitor(self, monitor):
  31. monitor['users'] = self.count()
  32. monitor['users_inactive'] = self.filter(activation__gt=0).count()
  33. last_user = self.latest('id')
  34. monitor['last_user'] = last_user.pk
  35. monitor['last_user_name'] = last_user.username
  36. monitor['last_user_slug'] = last_user.username_slug
  37. def create_user(self, username, email, password, timezone=False, ip='127.0.0.1', activation=0, request=False):
  38. token = ''
  39. if activation > 0:
  40. token = get_random_string(12)
  41. if timezone == False:
  42. try:
  43. timezone = request.settings['default_timezone']
  44. db_settings = request.settings
  45. except AttributeError:
  46. db_settings = DBSettings()
  47. timezone = db_settings['default_timezone']
  48. # Get first rank
  49. try:
  50. default_rank = Rank.objects.filter(special=0).order_by('order')[0]
  51. except Rank.DoesNotExist:
  52. default_rank = None
  53. # Store user in database
  54. new_user = User(
  55. join_date=tz_util.now(),
  56. join_ip=ip,
  57. activation=activation,
  58. token=token,
  59. timezone=timezone,
  60. rank=default_rank,
  61. )
  62. new_user.set_username(username)
  63. new_user.set_email(email)
  64. new_user.set_password(password)
  65. new_user.full_clean()
  66. new_user.default_avatar(db_settings)
  67. new_user.save(force_insert=True)
  68. # Set user roles
  69. new_user.roles.add(Role.objects.get(token='registered'))
  70. new_user.save(force_update=True)
  71. # Load monitor
  72. try:
  73. monitor = request.monitor
  74. except AttributeError:
  75. monitor = Monitor()
  76. # Update forum stats
  77. if activation == 0:
  78. monitor['users'] = int(monitor['users']) + 1
  79. monitor['last_user'] = new_user.pk
  80. monitor['last_user_name'] = new_user.username
  81. monitor['last_user_slug'] = new_user.username_slug
  82. else:
  83. monitor['users_inactive'] = int(monitor['users_inactive']) + 1
  84. # Return new user
  85. return new_user
  86. def get_by_email(self, email):
  87. return self.get(email_hash=hashlib.md5(email).hexdigest())
  88. def filter_overview(self, start, end):
  89. return self.filter(join_date__gte=start).filter(join_date__lte=end)
  90. class User(models.Model):
  91. """
  92. Misago User model
  93. """
  94. username = models.CharField(max_length=255,validators=[validate_username])
  95. username_slug = models.SlugField(max_length=255,unique=True,
  96. error_messages={'unique': _("This user name is already in use by another user.")})
  97. email = models.EmailField(max_length=255,validators=[validate_email])
  98. email_hash = models.CharField(max_length=32,unique=True,
  99. error_messages={'unique': _("This email address is already in use by another user.")})
  100. password = models.CharField(max_length=255)
  101. password_date = models.DateTimeField()
  102. avatar_type = models.CharField(max_length=10,null=True,blank=True)
  103. avatar_image = models.CharField(max_length=255,null=True,blank=True)
  104. signature = models.TextField(null=True,blank=True)
  105. signature_preparsed = models.TextField(null=True,blank=True)
  106. join_date = models.DateTimeField()
  107. join_ip = models.GenericIPAddressField()
  108. join_agent = models.TextField(null=True,blank=True)
  109. last_date = models.DateTimeField(null=True,blank=True)
  110. last_ip = models.GenericIPAddressField(null=True,blank=True)
  111. last_agent = models.TextField(null=True,blank=True)
  112. hide_activity = models.PositiveIntegerField(default=0)
  113. topics = models.PositiveIntegerField(default=0)
  114. topics_delta = models.IntegerField(default=0)
  115. posts = models.PositiveIntegerField(default=0)
  116. posts_delta = models.IntegerField(default=0)
  117. karma = models.IntegerField(default=0)
  118. karma_delta = models.IntegerField(default=0)
  119. followers = models.PositiveIntegerField(default=0)
  120. followers_delta = models.IntegerField(default=0)
  121. score = models.IntegerField(default=0,db_index=True)
  122. rank = models.ForeignKey('Rank',null=True,blank=True,db_index=True,on_delete=models.SET_NULL)
  123. title = models.CharField(max_length=255,null=True,blank=True)
  124. last_post = models.DateTimeField(null=True,blank=True)
  125. last_search = models.DateTimeField(null=True,blank=True)
  126. alerts = models.PositiveIntegerField(default=0)
  127. alerts_new = models.PositiveIntegerField(default=0)
  128. activation = models.IntegerField(default=0)
  129. token = models.CharField(max_length=12,null=True,blank=True)
  130. avatar_ban = models.BooleanField(default=False)
  131. avatar_ban_reason_user = models.TextField(null=True,blank=True)
  132. avatar_ban_reason_admin = models.TextField(null=True,blank=True)
  133. signature_ban = models.BooleanField(default=False)
  134. signature_ban_reason_user = models.TextField(null=True,blank=True)
  135. signature_ban_reason_admin = models.TextField(null=True,blank=True)
  136. timezone = models.CharField(max_length=255,default='utc')
  137. roles = models.ManyToManyField(Role)
  138. acl_cache = models.TextField(null=True,blank=True)
  139. objects = UserManager()
  140. ACTIVATION_NONE = 0
  141. ACTIVATION_USER = 1
  142. ACTIVATION_ADMIN = 2
  143. ACTIVATION_CREDENTIALS = 3
  144. statistics_name = _('Users Registrations')
  145. def acl(self):
  146. pass
  147. def is_admin(self):
  148. if self.is_god():
  149. return True
  150. return False #TODO!
  151. def is_god(self):
  152. try:
  153. return self.is_god_cache
  154. except AttributeError:
  155. for user in settings.ADMINS:
  156. if user[1].lower() == self.email:
  157. self.is_god_cache = True
  158. return True
  159. self.is_god_cache = False
  160. return False
  161. def is_anonymous(self):
  162. return False
  163. def is_authenticated(self):
  164. return True
  165. def is_crawler(self):
  166. return False
  167. def is_protected(self):
  168. for role in self.roles.all():
  169. if role.protected:
  170. return True
  171. return False
  172. def lock_avatar(self):
  173. # Kill existing avatar and lock our ability to change it
  174. self.delete_avatar()
  175. self.avatar_ban = True
  176. # Pick new one from _locked gallery
  177. galleries = path(settings.STATICFILES_DIRS[0]).joinpath('avatars').joinpath('_locked')
  178. avatars_list = galleries.files('*.gif')
  179. avatars_list += galleries.files('*.jpg')
  180. avatars_list += galleries.files('*.jpeg')
  181. avatars_list += galleries.files('*.png')
  182. self.avatar_type = 'gallery'
  183. self.avatar_image = '/'.join(path(choice(avatars_list)).splitall()[-2:])
  184. def default_avatar(self, db_settings):
  185. if db_settings['default_avatar'] == 'gallery':
  186. try:
  187. avatars_list = []
  188. try:
  189. # First try, _default path
  190. galleries = path(settings.STATICFILES_DIRS[0]).joinpath('avatars').joinpath('_default')
  191. avatars_list += galleries.files('*.gif')
  192. avatars_list += galleries.files('*.jpg')
  193. avatars_list += galleries.files('*.jpeg')
  194. avatars_list += galleries.files('*.png')
  195. except Exception as e:
  196. pass
  197. # Second try, all paths
  198. if not avatars_list:
  199. avatars_list = []
  200. for directory in path(settings.STATICFILES_DIRS[0]).joinpath('avatars').dirs():
  201. avatars_list += directory.files('*.gif')
  202. avatars_list += directory.files('*.jpg')
  203. avatars_list += directory.files('*.jpeg')
  204. avatars_list += directory.files('*.png')
  205. if avatars_list:
  206. # Pick random avatar from list
  207. self.avatar_type = 'gallery'
  208. self.avatar_image = '/'.join(path(choice(avatars_list)).splitall()[-2:])
  209. return True
  210. except Exception as e:
  211. pass
  212. self.avatar_type = 'gravatar'
  213. self.avatar_image = None
  214. return True
  215. def delete_avatar(self):
  216. if self.avatar_type == 'upload':
  217. # DELETE OUR AVATAR!!!
  218. pass
  219. def delete(self, *args, **kwargs):
  220. self.delete_avatar()
  221. super(User, self).delete(*args, **kwargs)
  222. def set_username(self, username):
  223. self.username = username.strip()
  224. self.username_slug = slugify(username)
  225. def set_signature(self, signature):
  226. self.signature = signature.strip()
  227. self.signature_preparsed = ''
  228. if self.signature:
  229. import markdown
  230. self.signature_preparsed = markdown.markdown(value, safe_mode='escape', output_format=format)
  231. def is_username_valid(self, e):
  232. try:
  233. raise ValidationError(e.message_dict['username'])
  234. except KeyError:
  235. pass
  236. try:
  237. raise ValidationError(e.message_dict['username_slug'])
  238. except KeyError:
  239. pass
  240. def is_email_valid(self, e):
  241. try:
  242. raise ValidationError(e.message_dict['email'])
  243. except KeyError:
  244. pass
  245. try:
  246. raise ValidationError(e.message_dict['email_hash'])
  247. except KeyError:
  248. pass
  249. def is_password_valid(self, e):
  250. try:
  251. raise ValidationError(e.message_dict['password'])
  252. except KeyError:
  253. pass
  254. def set_email(self, email):
  255. self.email = email.strip().lower()
  256. self.email_hash = hashlib.md5(self.email).hexdigest()
  257. def set_password(self, raw_password):
  258. self.password_date = tz_util.now()
  259. self.password = make_password(raw_password.strip())
  260. def set_last_visit(self, ip, agent, hidden=False):
  261. self.last_date = tz_util.now()
  262. self.last_ip = ip
  263. self.last_agent = agent
  264. self.last_hide = hidden
  265. def check_password(self, raw_password, mobile=False):
  266. """
  267. Returns a boolean of whether the raw_password was correct. Handles
  268. hashing formats behind the scenes.
  269. """
  270. def setter(raw_password):
  271. self.set_password(raw_password)
  272. self.save()
  273. # Is standard password allright?
  274. if check_password(raw_password, self.password, setter):
  275. return True
  276. # Check mobile password?
  277. if mobile:
  278. raw_password = raw_password[:1].lower() + raw_password[1:]
  279. else:
  280. password_reversed = u''
  281. for c in raw_password:
  282. r = c.upper()
  283. if r == c:
  284. r = c.lower()
  285. password_reversed += r
  286. raw_password = password_reversed
  287. return check_password(raw_password, self.password, setter)
  288. def get_avatar(self, size='normal'):
  289. # Get uploaded avatar
  290. if self.avatar_type == 'upload':
  291. return settings.MEDIA_URL + 'avatars/' + self.avatar_image
  292. # Get gallery avatar
  293. if self.avatar_type == 'gallery':
  294. return settings.STATIC_URL + 'avatars/' + self.avatar_image
  295. # No avatar found, get gravatar
  296. if size == 'big':
  297. size = 150;
  298. elif size == 'small':
  299. size = 64;
  300. elif size == 'tiny':
  301. size = 46;
  302. else:
  303. size = 100
  304. return 'http://www.gravatar.com/avatar/%s?s=%s' % (hashlib.md5(self.email).hexdigest(), size)
  305. def get_title(self):
  306. if self.title:
  307. return self.title
  308. if self.rank:
  309. return self.rank.title
  310. return None
  311. def email_user(self, request, template, subject, context={}):
  312. templates = request.theme.get_email_templates(template)
  313. context = RequestContext(request, context)
  314. context['author'] = context['user']
  315. context['user'] = self
  316. # Set message recipient
  317. if settings.DEBUG and settings.CATCH_ALL_EMAIL_ADDRESS:
  318. recipient = settings.CATCH_ALL_EMAIL_ADDRESS
  319. else:
  320. recipient = self.email
  321. # Build and send message
  322. email = EmailMultiAlternatives(subject, templates[0].render(context), settings.EMAIL_HOST_USER, [recipient])
  323. email.attach_alternative(templates[1].render(context), "text/html")
  324. email.send()
  325. def get_activation(self):
  326. activations = ['none', 'user', 'admin', 'credentials']
  327. return activations[self.activation]
  328. def get_date(self):
  329. return self.join_date
  330. class Guest(object):
  331. """
  332. Misago Guest dummy
  333. """
  334. def is_admin(self):
  335. return False
  336. def is_anonymous(self):
  337. return True
  338. def is_authenticated(self):
  339. return False
  340. def is_crawler(self):
  341. return False
  342. class Crawler(object):
  343. """
  344. Misago Crawler dummy
  345. """
  346. def __init__(self, username):
  347. self.username = username
  348. def is_admin(self):
  349. return False
  350. def is_anonymous(self):
  351. return True
  352. def is_authenticated(self):
  353. return False
  354. def is_crawler(self):
  355. return True
  356. class Rank(models.Model):
  357. """
  358. Misago User Rank
  359. Ranks are ready style/title pairs that are assigned to users either by admin (special ranks) or as result of user activity.
  360. """
  361. name = models.CharField(max_length=255)
  362. name_slug = models.CharField(max_length=255,null=True,blank=True)
  363. description = models.TextField(null=True,blank=True)
  364. style = models.CharField(max_length=255,null=True,blank=True)
  365. title = models.CharField(max_length=255,null=True,blank=True)
  366. special = models.BooleanField(default=False)
  367. as_tab = models.BooleanField(default=False)
  368. order = models.IntegerField(default=0)
  369. criteria = models.CharField(max_length=255,null=True,blank=True)
  370. def __unicode__(self):
  371. return unicode(_(self.name))
  372. def assign_rank(self, users=0, special_ranks=None):
  373. if not self.criteria or self.special or users == 0:
  374. # Rank cant be rolled in
  375. return False
  376. if self.criteria == "0":
  377. # Just update all fellows
  378. User.objects.exclude(rank__in=special_ranks).update(rank=self)
  379. else:
  380. # Count number of users to update
  381. if self.criteria[-1] == '%':
  382. criteria = int(self.criteria[0:-1])
  383. criteria = int(math.ceil(float(users / 100.0)* criteria))
  384. else:
  385. criteria = int(self.criteria)
  386. # Join special ranks
  387. if special_ranks:
  388. special_ranks = ','.join(special_ranks)
  389. # Run raw query
  390. cursor = connection.cursor()
  391. try:
  392. # Postgresql
  393. if (settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql_psycopg2'
  394. or settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql'):
  395. if special_ranks:
  396. cursor.execute('''UPDATE users_user
  397. FROM (
  398. SELECT id
  399. FROM users_user
  400. WHERE rank_id NOT IN (%s)
  401. ORDER BY score DESC LIMIT %s
  402. ) AS updateable
  403. SET rank_id=%s
  404. WHERE id = updateable.id
  405. RETURNING *''' % (self.id, special_ranks, criteria))
  406. else:
  407. cursor.execute('''UPDATE users_user
  408. FROM (
  409. SELECT id
  410. FROM users_user
  411. ORDER BY score DESC LIMIT %s
  412. ) AS updateable
  413. SET rank_id=%s
  414. WHERE id = updateable.id
  415. RETURNING *''', [self.id, criteria])
  416. # MySQL, SQLite and Oracle
  417. if (settings.DATABASES['default']['ENGINE'] == 'django.db.backends.mysql'
  418. or settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3'
  419. or settings.DATABASES['default']['ENGINE'] == 'django.db.backends.oracle'):
  420. if special_ranks:
  421. cursor.execute('''UPDATE users_user
  422. SET rank_id=%s
  423. WHERE rank_id NOT IN (%s)
  424. ORDER BY score DESC
  425. LIMIT %s''' % (self.id, special_ranks, criteria))
  426. else:
  427. cursor.execute('''UPDATE users_user
  428. SET rank_id=%s
  429. ORDER BY score DESC
  430. LIMIT %s''', [self.id, criteria])
  431. except Exception as e:
  432. print 'Error updating users ranking: %s' % e
  433. transaction.commit_unless_managed()
  434. return True
  435. class Follower(models.Model):
  436. """
  437. Misago Users follow model
  438. """
  439. user = models.ForeignKey('User',related_name='+')
  440. target = models.ForeignKey('User')
  441. since = models.DateTimeField()
  442. class Foe(models.Model):
  443. """
  444. Misago Users foes model
  445. """
  446. user = models.ForeignKey('User')
  447. target = models.ForeignKey('User',related_name='+')
  448. since = models.DateTimeField()