usermodel.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. import hashlib
  2. from datetime import timedelta
  3. import math
  4. from random import choice
  5. from path import path
  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 acl
  16. from misago.apps.profiles.warnings.warningstracker import WarningsTracker
  17. from misago.conf import settings
  18. from misago.monitor import monitor, UpdatingMonitor
  19. from misago.signals import delete_user_content, rename_user, sync_user_profile
  20. from misago.template.loader import render_to_string
  21. from misago.utils.avatars import avatar_size
  22. from misago.utils.strings import random_string, slugify
  23. from misago.validators import validate_username, validate_password, validate_email
  24. class UserManager(models.Manager):
  25. """
  26. User Manager provides us with some additional methods for users
  27. """
  28. def get_blank_user(self):
  29. blank_user = User(
  30. join_date=tz_util.now(),
  31. join_ip='127.0.0.1'
  32. )
  33. return blank_user
  34. def resync_monitor(self):
  35. with UpdatingMonitor() as cm:
  36. monitor['users'] = self.filter(activation=0).count()
  37. monitor['users_inactive'] = self.filter(activation__gt=0).count()
  38. last_user = self.filter(activation=0).latest('id')
  39. monitor['last_user'] = last_user.pk
  40. monitor['last_user_name'] = last_user.username
  41. monitor['last_user_slug'] = last_user.username_slug
  42. def create_user(self, username, email, password, timezone=False, ip='127.0.0.1', agent='', no_roles=False, activation=0, request=False):
  43. token = ''
  44. if activation > 0:
  45. token = random_string(12)
  46. timezone = timezone or settings.default_timezone
  47. # Get first rank
  48. try:
  49. from misago.models import Rank
  50. default_rank = Rank.objects.filter(special=0).order_by('-order')[0]
  51. except IndexError:
  52. default_rank = None
  53. # Store user in database
  54. new_user = User(
  55. last_sync=tz_util.now(),
  56. join_date=tz_util.now(),
  57. join_ip=ip,
  58. join_agent=agent,
  59. activation=activation,
  60. token=token,
  61. timezone=timezone,
  62. rank=default_rank,
  63. subscribe_start=settings.subscribe_start,
  64. subscribe_reply=settings.subscribe_reply,
  65. )
  66. validate_username(username)
  67. validate_password(password)
  68. new_user.set_username(username)
  69. new_user.set_email(email)
  70. new_user.set_password(password)
  71. new_user.full_clean()
  72. new_user.default_avatar()
  73. new_user.save(force_insert=True)
  74. # Set user roles?
  75. if not no_roles:
  76. from misago.models import Role
  77. new_user.roles.add(Role.objects.get(_special='registered'))
  78. new_user.make_acl_key()
  79. new_user.save(force_update=True)
  80. # Update forum stats
  81. with UpdatingMonitor() as cm:
  82. if activation == 0:
  83. monitor.increase('users')
  84. monitor['last_user'] = new_user.pk
  85. monitor['last_user_name'] = new_user.username
  86. monitor['last_user_slug'] = new_user.username_slug
  87. else:
  88. monitor.increase('users_inactive')
  89. # Return new user
  90. return new_user
  91. def get_by_email(self, email):
  92. return self.get(email_hash=hashlib.md5(email.lower().encode('utf-8')).hexdigest())
  93. def filter_stats(self, start, end):
  94. return self.filter(join_date__gte=start).filter(join_date__lte=end)
  95. def block_user(self, user):
  96. return User.objects.select_for_update().get(id=user.id)
  97. class User(models.Model):
  98. """
  99. Misago User model
  100. """
  101. username = models.CharField(max_length=255)
  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. _avatar_crop = models.CharField(max_length=255, null=True, blank=True, db_column='avatar_crop')
  114. signature = models.TextField(null=True, blank=True)
  115. signature_preparsed = models.TextField(null=True, blank=True)
  116. join_date = models.DateTimeField()
  117. join_ip = models.GenericIPAddressField()
  118. join_agent = models.TextField(null=True, blank=True)
  119. last_date = models.DateTimeField(null=True, blank=True)
  120. last_ip = models.GenericIPAddressField(null=True, blank=True)
  121. last_agent = models.TextField(null=True, blank=True)
  122. hide_activity = models.PositiveIntegerField(default=0)
  123. subscribe_start = models.PositiveIntegerField(default=0)
  124. subscribe_reply = models.PositiveIntegerField(default=0)
  125. receive_newsletters = models.BooleanField(default=True)
  126. threads = models.PositiveIntegerField(default=0)
  127. posts = models.PositiveIntegerField(default=0)
  128. votes = models.PositiveIntegerField(default=0)
  129. karma_given_p = models.PositiveIntegerField(default=0)
  130. karma_given_n = models.PositiveIntegerField(default=0)
  131. karma_p = models.PositiveIntegerField(default=0)
  132. karma_n = models.PositiveIntegerField(default=0)
  133. following = models.PositiveIntegerField(default=0)
  134. followers = models.PositiveIntegerField(default=0)
  135. score = models.IntegerField(default=0)
  136. ranking = models.PositiveIntegerField(default=0)
  137. rank = models.ForeignKey('Rank', null=True, blank=True, on_delete=models.SET_NULL)
  138. last_sync = models.DateTimeField(null=True, blank=True)
  139. follows = models.ManyToManyField('self', related_name='follows_set', symmetrical=False)
  140. ignores = models.ManyToManyField('self', related_name='ignores_set', symmetrical=False)
  141. title = models.CharField(max_length=255, null=True, blank=True)
  142. last_post = models.DateTimeField(null=True, blank=True)
  143. last_vote = models.DateTimeField(null=True, blank=True)
  144. last_search = models.DateTimeField(null=True, blank=True)
  145. alerts = models.PositiveIntegerField(default=0)
  146. alerts_date = models.DateTimeField(null=True, blank=True)
  147. allow_pds = models.PositiveIntegerField(default=0)
  148. unread_pds = models.PositiveIntegerField(default=0)
  149. sync_pds = models.BooleanField(default=False)
  150. activation = models.IntegerField(default=0)
  151. token = models.CharField(max_length=12, null=True, blank=True)
  152. avatar_ban = models.BooleanField(default=False)
  153. avatar_ban_reason_user = models.TextField(null=True, blank=True)
  154. avatar_ban_reason_admin = models.TextField(null=True, blank=True)
  155. signature_ban = models.BooleanField(default=False)
  156. signature_ban_reason_user = models.TextField(null=True, blank=True)
  157. signature_ban_reason_admin = models.TextField(null=True, blank=True)
  158. timezone = models.CharField(max_length=255, default='utc')
  159. roles = models.ManyToManyField('Role')
  160. is_team = models.BooleanField(default=False)
  161. acl_key = models.CharField(max_length=12, null=True, blank=True)
  162. warning_level = models.PositiveIntegerField(default=0)
  163. warning_level_update_on = models.DateTimeField(null=True, blank=True)
  164. objects = UserManager()
  165. ACTIVATION_NONE = 0
  166. ACTIVATION_USER = 1
  167. ACTIVATION_ADMIN = 2
  168. ACTIVATION_CREDENTIALS = 3
  169. statistics_name = _('Users Registrations')
  170. class Meta:
  171. app_label = 'misago'
  172. def is_god(self):
  173. try:
  174. return self.is_god_cache
  175. except AttributeError:
  176. for user in settings.ADMINS:
  177. if user[1].lower() == self.email:
  178. self.is_god_cache = True
  179. return True
  180. self.is_god_cache = False
  181. return False
  182. def is_anonymous(self):
  183. return False
  184. def is_authenticated(self):
  185. return True
  186. def is_crawler(self):
  187. return False
  188. def is_protected(self):
  189. for role in self.roles.all():
  190. if role.protected:
  191. return True
  192. return False
  193. def lock_avatar(self):
  194. # Kill existing avatar and lock our ability to change it
  195. self.delete_avatar()
  196. self.avatar_ban = True
  197. # Pick new one from _locked gallery
  198. galleries = path(settings.STATICFILES_DIRS[0]).joinpath('avatars').joinpath('_locked')
  199. avatars_list = galleries.files('*.gif')
  200. avatars_list += galleries.files('*.jpg')
  201. avatars_list += galleries.files('*.jpeg')
  202. avatars_list += galleries.files('*.png')
  203. self.avatar_type = 'gallery'
  204. self.avatar_image = '/'.join(path(choice(avatars_list)).splitall()[-2:])
  205. def default_avatar(self):
  206. if settings.default_avatar == 'gallery':
  207. try:
  208. avatars_list = []
  209. try:
  210. # First try, _default path
  211. galleries = path(settings.STATICFILES_DIRS[0]).joinpath('avatars').joinpath('_default')
  212. avatars_list += galleries.files('*.gif')
  213. avatars_list += galleries.files('*.jpg')
  214. avatars_list += galleries.files('*.jpeg')
  215. avatars_list += galleries.files('*.png')
  216. except Exception as e:
  217. pass
  218. # Second try, all paths
  219. if not avatars_list:
  220. avatars_list = []
  221. for directory in path(settings.STATICFILES_DIRS[0]).joinpath('avatars').dirs():
  222. if not directory[-7:] == '_locked' and not directory[-7:] == '_thumbs':
  223. avatars_list += directory.files('*.gif')
  224. avatars_list += directory.files('*.jpg')
  225. avatars_list += directory.files('*.jpeg')
  226. avatars_list += directory.files('*.png')
  227. if avatars_list:
  228. # Pick random avatar from list
  229. self.avatar_type = 'gallery'
  230. self.avatar_image = '/'.join(path(choice(avatars_list)).splitall()[-2:])
  231. return True
  232. except Exception as e:
  233. pass
  234. self.avatar_type = 'gravatar'
  235. self.avatar_image = None
  236. return True
  237. def delete_avatar_temp(self):
  238. if self.avatar_temp:
  239. try:
  240. av_file = path(settings.MEDIA_ROOT + 'avatars/' + self.avatar_temp)
  241. if not av_file.isdir():
  242. av_file.remove()
  243. except Exception:
  244. pass
  245. self.avatar_temp = None
  246. def delete_avatar_original(self):
  247. if self.avatar_original:
  248. try:
  249. av_file = path(settings.MEDIA_ROOT + 'avatars/' + self.avatar_original)
  250. if not av_file.isdir():
  251. av_file.remove()
  252. except Exception:
  253. pass
  254. self.avatar_original = None
  255. def delete_avatar_image(self):
  256. if self.avatar_image:
  257. for size in settings.AVATAR_SIZES[1:]:
  258. try:
  259. av_file = path(settings.MEDIA_ROOT + 'avatars/' + str(size) + '_' + self.avatar_image)
  260. if not av_file.isdir():
  261. av_file.remove()
  262. except Exception:
  263. pass
  264. try:
  265. av_file = path(settings.MEDIA_ROOT + 'avatars/' + self.avatar_image)
  266. if not av_file.isdir():
  267. av_file.remove()
  268. except Exception:
  269. pass
  270. self.avatar_image = None
  271. def delete_avatar(self):
  272. self.delete_avatar_temp()
  273. self.delete_avatar_original()
  274. self.delete_avatar_image()
  275. def delete_content(self):
  276. delete_user_content.send(sender=self)
  277. def delete(self, *args, **kwargs):
  278. self.delete_avatar()
  279. super(User, self).delete(*args, **kwargs)
  280. def set_username(self, username):
  281. self.username = username.strip()
  282. self.username_slug = slugify(username).replace('-', '')
  283. def sync_username(self):
  284. rename_user.send(sender=self)
  285. def is_username_valid(self, e):
  286. try:
  287. raise ValidationError(e.message_dict['username'])
  288. except KeyError:
  289. pass
  290. try:
  291. raise ValidationError(e.message_dict['username_slug'])
  292. except KeyError:
  293. pass
  294. def is_email_valid(self, e):
  295. try:
  296. raise ValidationError(e.message_dict['email'])
  297. except KeyError:
  298. pass
  299. try:
  300. raise ValidationError(e.message_dict['email_hash'])
  301. except KeyError:
  302. pass
  303. def is_password_valid(self, e):
  304. try:
  305. raise ValidationError(e.message_dict['password'])
  306. except KeyError:
  307. pass
  308. def set_email(self, email):
  309. self.email = email.strip().lower()
  310. self.email_hash = hashlib.md5(self.email.encode('utf-8')).hexdigest()
  311. def set_password(self, raw_password):
  312. self.password_date = tz_util.now()
  313. self.password = make_password(raw_password.strip())
  314. def set_last_visit(self, ip, agent):
  315. self.last_date = tz_util.now()
  316. self.last_ip = ip
  317. self.last_agent = agent
  318. def check_password(self, raw_password, mobile=False):
  319. """
  320. Returns a boolean of whether the raw_password was correct. Handles
  321. hashing formats behind the scenes.
  322. """
  323. def setter(raw_password):
  324. self.set_password(raw_password)
  325. self.save()
  326. # Is standard password allright?
  327. if check_password(raw_password, self.password, setter):
  328. return True
  329. # Check mobile password?
  330. if mobile:
  331. raw_password = raw_password[:1].lower() + raw_password[1:]
  332. else:
  333. password_reversed = u''
  334. for c in raw_password:
  335. r = c.upper()
  336. if r == c:
  337. r = c.lower()
  338. password_reversed += r
  339. raw_password = password_reversed
  340. return check_password(raw_password, self.password, setter)
  341. def is_following(self, user):
  342. try:
  343. return self.follows.filter(id=user.pk).count() > 0
  344. except AttributeError:
  345. return self.follows.filter(id=user).count() > 0
  346. def is_ignoring(self, user):
  347. try:
  348. return self.ignores.filter(id=user.pk).count() > 0
  349. except AttributeError:
  350. return self.ignores.filter(id=user).count() > 0
  351. def ignored_users(self):
  352. return [item['id'] for item in self.ignores.values('id')]
  353. def allow_pd_invite(self, user):
  354. # PD's from nobody
  355. if self.allow_pds == 3:
  356. return False
  357. # PD's from followed
  358. if self.allow_pds == 2:
  359. return self.is_following(user)
  360. # PD's from non-ignored
  361. if self.allow_pds == 1:
  362. return not self.is_ignoring(user)
  363. return True
  364. def get_roles(self):
  365. if self.rank:
  366. return self.roles.all() | self.rank.roles.all()
  367. return self.roles.all()
  368. def make_acl_key(self, force=False):
  369. if not force and self.acl_key:
  370. return self.acl_key
  371. roles_ids = []
  372. for role in self.roles.all():
  373. roles_ids.append(role.pk)
  374. if self.rank:
  375. for role in self.rank.roles.all():
  376. if not role.pk in roles_ids:
  377. roles_ids.append(role.pk)
  378. roles_ids.sort()
  379. self.acl_key = 'acl_%s' % hashlib.md5('_'.join(str(x) for x in roles_ids)).hexdigest()[0:8]
  380. self.save(update_fields=('acl_key',))
  381. return self.acl_key
  382. def acl(self):
  383. return acl(self)
  384. @property
  385. def avatar_crop(self):
  386. return [int(float(x)) for x in self._avatar_crop.split(',')] if self._avatar_crop else (0, 0, 100, 100)
  387. @avatar_crop.setter
  388. def avatar_crop(self, value):
  389. self._avatar_crop = ','.join(value)
  390. def get_avatar(self, size=None):
  391. image_size = avatar_size(size) if size else None
  392. # Get uploaded avatar
  393. if self.avatar_type == 'upload':
  394. image_prefix = '%s_' % image_size if image_size else ''
  395. return settings.MEDIA_URL + 'avatars/' + image_prefix + self.avatar_image
  396. # Get gallery avatar
  397. if self.avatar_type == 'gallery':
  398. image_prefix = '_thumbs/%s/' % image_size if image_size else ''
  399. return settings.STATIC_URL + 'avatars/' + image_prefix + self.avatar_image
  400. # No avatar found, get gravatar
  401. if not image_size:
  402. image_size = settings.AVATAR_SIZES[0]
  403. # Decide on default gravatar
  404. gravatar_default = ''
  405. if (settings.GRAVATAR_DEFAULT
  406. and not '&' in settings.GRAVATAR_DEFAULT
  407. and not '?' in settings.GRAVATAR_DEFAULT):
  408. gravatar_default = '&d=%s' % settings.GRAVATAR_DEFAULT
  409. return 'http://www.gravatar.com/avatar/%s?s=%s%s' % (hashlib.md5(self.email.encode('utf-8')).hexdigest(), image_size, gravatar_default)
  410. def get_ranking(self):
  411. if not self.ranking:
  412. self.ranking = User.objects.filter(score__gt=self.score).count() + 1
  413. self.save(force_update=True)
  414. return self.ranking
  415. def get_title(self):
  416. if self.title:
  417. return self.title
  418. if self.rank:
  419. return self.rank.title
  420. return None
  421. def get_style(self):
  422. if self.rank:
  423. return self.rank.style
  424. return ''
  425. def email_user(self, request, template, subject, context={}):
  426. context = RequestContext(request, context)
  427. context['author'] = context['user']
  428. context['user'] = self
  429. email_html = render_to_string('_email/%s.html' % template,
  430. context_instance=context)
  431. email_text = render_to_string('_email/%s.txt' % template,
  432. context_instance=context)
  433. # Set message recipient
  434. if settings.DEBUG and settings.CATCH_ALL_EMAIL_ADDRESS:
  435. recipient = settings.CATCH_ALL_EMAIL_ADDRESS
  436. else:
  437. recipient = self.email
  438. # Set message author
  439. if settings.board_name:
  440. sender = '%s <%s>' % (settings.board_name.replace("<", "(").replace(">", ")"), settings.DEFAULT_FROM_EMAIL)
  441. else:
  442. sender = settings.DEFAULT_FROM_EMAIL
  443. # Build message and add it to queue
  444. email = EmailMultiAlternatives(subject, email_text, sender, [recipient])
  445. email.attach_alternative(email_html, "text/html")
  446. request.mails_queue.append(email)
  447. def get_activation(self):
  448. activations = ['none', 'user', 'admin', 'credentials']
  449. return activations[self.activation]
  450. def alert(self, message):
  451. from misago.models import Alert
  452. self.alerts += 1
  453. return Alert(user=self, message=message, date=tz_util.now())
  454. def sync_unread_pds(self, unread):
  455. self.unread_pds = unread
  456. self.sync_pds = False
  457. def get_date(self):
  458. return self.join_date
  459. def sync_profile(self):
  460. if (settings.PROFILES_SYNC_FREQUENCY > 0 and
  461. self.last_sync <= tz_util.now() - timedelta(days=settings.PROFILES_SYNC_FREQUENCY)):
  462. sync_user_profile.send(sender=self)
  463. self.last_sync = tz_util.now()
  464. return True
  465. return False
  466. def is_warning_level_expired(self):
  467. if self.warning_level and self.warning_level_update_on:
  468. return tz_util.now() > self.warning_level_update_on
  469. return False
  470. def update_expired_warning_level(self):
  471. self.warning_level -= 1
  472. try:
  473. from misago.models import WarnLevel
  474. warning_levels = WarnLevel.objects.get_levels()
  475. new_warning_level = warning_levels[self.warning_level]
  476. if new_warning_level.expires_after_minutes:
  477. self.warning_level_update_on -= timedelta(
  478. minutes=new_warning_level.expires_after_minutes)
  479. else:
  480. self.warning_level_update_on = None
  481. except KeyError:
  482. # Break expiration chain so infinite loop won't happen
  483. # This should only happen if your warning level is 0, but
  484. # will also keep app responsive if data corruption happens
  485. self.warning_level_update_on = None
  486. def get_warning_level(self):
  487. if self.warning_level:
  488. from misago.models import WarnLevel
  489. return WarnLevel.objects.get_level(
  490. self.warning_level)
  491. else:
  492. return None
  493. def get_current_warning_level(self):
  494. if self.is_warning_level_expired():
  495. while self.update_expired_warning_level():
  496. self.update_warning_level()
  497. self.save(force_update=True)
  498. return self.get_warning_level()
  499. def get_latest_activte_warning(self):
  500. return self.warning_set.filter(canceled=False).order_by('-id')[:1][0]
  501. def freeze_warning_level(self):
  502. self.warning_level_update_on = tz_util.now() + timedelta(days=1)
  503. def set_warning_level_update_date(self, warning, warning_level):
  504. if warning_level.expires_after_minutes:
  505. self.warning_level_update_on = warning.given_on + timedelta(
  506. minutes=warning_level.expires_after_minutes)
  507. else:
  508. self.warning_level_update_on = None
  509. def decrease_warning_level(self):
  510. if self.get_current_warning_level():
  511. self.warning_level -= 1
  512. if self.warning_level:
  513. self.freeze_warning_level()
  514. latest_warning = self.get_latest_activte_warning()
  515. new_warning_level = self.get_current_warning_level()
  516. self.set_warning_level_update_date(
  517. latest_warning, new_warning_level)
  518. self.get_current_warning_level()
  519. else:
  520. self.warning_level_update_on = None
  521. self.save(force_update=True)
  522. def is_warning_active(self, warning):
  523. warning_level = self.get_warning_level()
  524. warnings_tracker = WarningsTracker(self.warning_level)
  525. for db_warning in self.warning_set.order_by('-pk').iterator():
  526. if warnings_tracker.is_warning_active(db_warning):
  527. if warning.pk == db_warning.pk:
  528. return True
  529. return False
  530. @property
  531. def warning_level_moderate_new_threads(self):
  532. warning_level = self.get_current_warning_level()
  533. if warning_level:
  534. restriction_level = warning_level.restrict_posting_threads
  535. return restriction_level == warning_level.RESTRICT_MODERATOR_REVIEW
  536. else:
  537. return False
  538. @property
  539. def warning_level_disallows_writing_threads(self):
  540. warning_level = self.get_current_warning_level()
  541. if warning_level:
  542. restriction_level = warning_level.restrict_posting_threads
  543. return restriction_level == warning_level.RESTRICT_DISALLOW
  544. else:
  545. return False
  546. @property
  547. def warning_level_moderate_new_replies(self):
  548. warning_level = self.get_current_warning_level()
  549. if warning_level:
  550. restriction_level = warning_level.restrict_posting_replies
  551. return restriction_level == warning_level.RESTRICT_MODERATOR_REVIEW
  552. else:
  553. return False
  554. @property
  555. def warning_level_disallows_writing_replies(self):
  556. warning_level = self.get_current_warning_level()
  557. if warning_level:
  558. restriction_level = warning_level.restrict_posting_replies
  559. return restriction_level == warning_level.RESTRICT_DISALLOW
  560. else:
  561. return False
  562. def timeline(self, qs, length=100):
  563. posts = {}
  564. now = tz_util.now()
  565. for item in qs.iterator():
  566. diff = (now - item.timeline_date).days
  567. try:
  568. posts[diff] += 1
  569. except KeyError:
  570. posts[diff] = 1
  571. graph = []
  572. for i in reversed(range(100)):
  573. try:
  574. graph.append(posts[i])
  575. except KeyError:
  576. graph.append(0)
  577. return graph
  578. class Guest(object):
  579. """
  580. Misago Guest dummy
  581. """
  582. id = -1
  583. pk = -1
  584. is_team = False
  585. def is_anonymous(self):
  586. return True
  587. def is_authenticated(self):
  588. return False
  589. def is_crawler(self):
  590. return False
  591. def get_roles(self):
  592. from misago.models import Role
  593. return Role.objects.filter(_special='guest')
  594. def make_acl_key(self):
  595. return 'acl_guest'
  596. class Crawler(Guest):
  597. """
  598. Misago Crawler dummy
  599. """
  600. is_team = False
  601. def __init__(self, username):
  602. self.username = username
  603. def is_anonymous(self):
  604. return False
  605. def is_authenticated(self):
  606. return False
  607. def is_crawler(self):
  608. return True
  609. """
  610. Signals handlers
  611. """
  612. def sync_user_handler(sender, **kwargs):
  613. sender.following = sender.follows.count()
  614. sender.followers = sender.follows_set.count()
  615. sync_user_profile.connect(sync_user_handler, dispatch_uid="sync_user_follows")