models.py 17 KB

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