models.py 19 KB

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