models.py 18 KB

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