models.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. import hashlib
  2. from random import choice
  3. from django.conf import settings
  4. from django.contrib.auth.hashers import (
  5. check_password, make_password, is_password_usable, UNUSABLE_PASSWORD)
  6. from django.core.exceptions import ValidationError
  7. from django.core.mail import EmailMultiAlternatives
  8. from django.db import models
  9. from django.template import RequestContext
  10. from django.utils import timezone as tz_util
  11. from django.utils.translation import ugettext_lazy as _
  12. from misago.monitor.monitor import Monitor
  13. from misago.security import get_random_string
  14. from misago.settings.settings import Settings as DBSettings
  15. from misago.users.validators import validate_username, validate_password, validate_email
  16. from misago.utils import slugify
  17. from path import path
  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, group, timezone=False, ip='127.0.0.1', activation=0, request=False):
  36. token = ''
  37. if activation > 0:
  38. token = get_random_string(12)
  39. if timezone == False:
  40. try:
  41. timezone = request.settings['default_timezone']
  42. db_settings = request.settings
  43. except AttributeError:
  44. db_settings = DBSettings()
  45. timezone = db_settings['default_timezone']
  46. # Store user in database
  47. new_user = User(
  48. join_date=tz_util.now(),
  49. join_ip=ip,
  50. activation=activation,
  51. token=token,
  52. timezone=timezone,
  53. group=group,
  54. )
  55. new_user.set_username(username)
  56. new_user.set_email(email)
  57. new_user.set_password(password)
  58. new_user.full_clean()
  59. new_user.default_avatar(db_settings)
  60. new_user.save(force_insert=True)
  61. # Set second group and default avatar
  62. new_user.groups.add(group)
  63. new_user.save(force_update=True)
  64. # Load monitor
  65. try:
  66. monitor = request.monitor
  67. except AttributeError:
  68. monitor = Monitor()
  69. # Update forum stats
  70. if activation == 0:
  71. monitor['users'] = int(monitor['users']) + 1
  72. monitor['last_user'] = new_user.pk
  73. monitor['last_user_name'] = new_user.username
  74. monitor['last_user_slug'] = new_user.username_slug
  75. else:
  76. monitor['users_inactive'] = int(monitor['users_inactive']) + 1
  77. # Return new user
  78. return new_user
  79. def get_by_email(self, email):
  80. return self.get(email_hash=hashlib.md5(email).hexdigest())
  81. def filter_overview(self, start, end):
  82. return self.filter(join_date__gte=start).filter(join_date__lte=end)
  83. class User(models.Model):
  84. """
  85. Misago User model
  86. """
  87. username = models.CharField(max_length=255,validators=[validate_username])
  88. username_slug = models.SlugField(max_length=255,unique=True,
  89. error_messages={'unique': _("This user name is already in use by another user.")})
  90. email = models.EmailField(max_length=255,validators=[validate_email])
  91. email_hash = models.CharField(max_length=32,unique=True,
  92. error_messages={'unique': _("This email address is already in use by another user.")})
  93. password = models.CharField(max_length=255)
  94. password_date = models.DateTimeField()
  95. avatar_type = models.CharField(max_length=10,null=True,blank=True)
  96. avatar_image = models.CharField(max_length=255,null=True,blank=True)
  97. signature = models.TextField(null=True,blank=True)
  98. signature_preparsed = models.TextField(null=True,blank=True)
  99. join_date = models.DateTimeField()
  100. join_ip = models.GenericIPAddressField()
  101. join_agent = models.TextField(null=True,blank=True)
  102. last_date = models.DateTimeField(null=True,blank=True)
  103. last_ip = models.GenericIPAddressField(null=True,blank=True)
  104. last_agent = models.TextField(null=True,blank=True)
  105. hide_activity = models.BooleanField(default=False)
  106. topics = models.PositiveIntegerField(default=0)
  107. topics_delta = models.IntegerField(default=0)
  108. posts = models.PositiveIntegerField(default=0)
  109. posts_delta = models.IntegerField(default=0)
  110. karma = models.IntegerField(default=0)
  111. karma_delta = models.IntegerField(default=0)
  112. followers = models.PositiveIntegerField(default=0)
  113. followers_delta = models.IntegerField(default=0)
  114. score = models.FloatField(default=0,db_index=True)
  115. rank = models.ForeignKey('Rank',null=True,blank=True,db_index=True,on_delete=models.SET_NULL)
  116. rank_freezed = models.BooleanField(default=False,db_index=True)
  117. last_post = models.DateTimeField(null=True,blank=True)
  118. last_search = models.DateTimeField(null=True,blank=True)
  119. alerts = models.PositiveIntegerField(default=0)
  120. alerts_new = models.PositiveIntegerField(default=0)
  121. activation = models.IntegerField(default=0)
  122. token = models.CharField(max_length=12,null=True,blank=True)
  123. banned = models.BooleanField(default=False)
  124. ban_reason_admin = models.TextField(null=True,blank=True)
  125. ban_reason_user = models.TextField(null=True,blank=True)
  126. ban_expires = models.DateTimeField(null=True,blank=True)
  127. avatar_ban = models.BooleanField(default=False)
  128. avatar_ban_reason_user = models.TextField(null=True,blank=True)
  129. avatar_ban_reason_admin = models.TextField(null=True,blank=True)
  130. avatar_ban_expires = models.DateTimeField(null=True,blank=True)
  131. signature_ban = models.BooleanField(default=False)
  132. signature_ban_reason_user = models.TextField(null=True,blank=True)
  133. signature_ban_reason_admin = models.TextField(null=True,blank=True)
  134. signature_ban_expires = models.DateTimeField(null=True,blank=True)
  135. timezone = models.CharField(max_length=255,default='utc')
  136. roles = models.CommaSeparatedIntegerField(max_length=255,null=True,blank=True)
  137. group = models.ForeignKey('Group',on_delete=models.PROTECT)
  138. groups = models.ManyToManyField('Group',related_name='groups')
  139. acl_cache = models.TextField(null=True,blank=True)
  140. objects = UserManager()
  141. ACTIVATION_NONE = 0
  142. ACTIVATION_USER = 1
  143. ACTIVATION_ADMIN = 2
  144. ACTIVATION_PASSWORD = 3
  145. statistics_name = _('Users Registrations')
  146. def is_admin(self):
  147. return 1 == self.group.pk
  148. def is_anonymous(self):
  149. return False
  150. def is_authenticated(self):
  151. return True
  152. def is_crawler(self):
  153. return False
  154. def is_banned(self):
  155. """
  156. Check if user is banned and handle eventual ban expiration.
  157. """
  158. banned = self.banned and (self.ban_expires == None or self.ban_expires > tz_util.now());
  159. if not banned and self.banned:
  160. self.banned = False
  161. self.save(force_update=True)
  162. return banned
  163. def default_avatar(self, db_settings):
  164. if db_settings['default_avatar'] == 'gallery':
  165. try:
  166. avatars_list = []
  167. try:
  168. # First try, _default path
  169. galleries = path(settings.STATICFILES_DIRS[0]).joinpath('avatars').joinpath('_default')
  170. avatars_list += galleries.files('*.gif')
  171. avatars_list += galleries.files('*.jpg')
  172. avatars_list += galleries.files('*.jpeg')
  173. avatars_list += galleries.files('*.png')
  174. except Exception as e:
  175. pass
  176. # Second try, all paths
  177. if not avatars_list:
  178. avatars_list = []
  179. for directory in path(settings.STATICFILES_DIRS[0]).joinpath('avatars').dirs():
  180. avatars_list += directory.files('*.gif')
  181. avatars_list += directory.files('*.jpg')
  182. avatars_list += directory.files('*.jpeg')
  183. avatars_list += directory.files('*.png')
  184. if avatars_list:
  185. # Pick random avatar from list
  186. self.avatar_type = 'gallery'
  187. self.avatar_image = '/'.join(path(choice(avatars_list)).splitall()[-2:])
  188. return True
  189. except Exception as e:
  190. pass
  191. self.avatar_type = 'gravatar'
  192. self.avatar_image = None
  193. return True
  194. def delete_avatar(self):
  195. if self.avatar_type == 'upload':
  196. # DELETE OUR AVATAR!!!
  197. pass
  198. def delete(self, *args, **kwargs):
  199. self.delete_avatar()
  200. super(User, self).delete(*args, **kwargs)
  201. def set_username(self, username):
  202. self.username = username.strip()
  203. self.username_slug = slugify(username)
  204. def is_username_valid(self, e):
  205. try:
  206. raise ValidationError(e.message_dict['username'])
  207. except KeyError:
  208. pass
  209. try:
  210. raise ValidationError(e.message_dict['username_slug'])
  211. except KeyError:
  212. pass
  213. def is_email_valid(self, e):
  214. try:
  215. raise ValidationError(e.message_dict['email'])
  216. except KeyError:
  217. pass
  218. try:
  219. raise ValidationError(e.message_dict['email_hash'])
  220. except KeyError:
  221. pass
  222. def is_password_valid(self, e):
  223. try:
  224. raise ValidationError(e.message_dict['password'])
  225. except KeyError:
  226. pass
  227. def set_email(self, email):
  228. self.email = email.strip().lower()
  229. self.email_hash = hashlib.md5(self.email).hexdigest()
  230. def set_password(self, raw_password):
  231. self.password_date = tz_util.now()
  232. self.password = make_password(raw_password.strip())
  233. def set_last_visit(self, ip, agent, hidden=False):
  234. self.last_date = tz_util.now()
  235. self.last_ip = ip
  236. self.last_agent = agent
  237. self.last_hide = hidden
  238. def check_password(self, raw_password, mobile=False):
  239. """
  240. Returns a boolean of whether the raw_password was correct. Handles
  241. hashing formats behind the scenes.
  242. """
  243. def setter(raw_password):
  244. self.set_password(raw_password)
  245. self.save()
  246. # Is standard password allright?
  247. if check_password(raw_password, self.password, setter):
  248. return True
  249. # Check mobile password?
  250. if mobile:
  251. raw_password = raw_password[:1].lower() + raw_password[1:]
  252. else:
  253. password_reversed = u''
  254. for c in raw_password:
  255. r = c.upper()
  256. if r == c:
  257. r = c.lower()
  258. password_reversed += r
  259. raw_password = password_reversed
  260. return check_password(raw_password, self.password, setter)
  261. def get_avatar(self, size='normal'):
  262. # Get uploaded avatar
  263. if self.avatar_type == 'upload':
  264. return settings.MEDIA_URL + 'avatars/' + self.avatar_image
  265. # Get gallery avatar
  266. if self.avatar_type == 'gallery':
  267. return settings.STATIC_URL + 'avatars/' + self.avatar_image
  268. # No avatar found, get gravatar
  269. if size == 'big':
  270. size = 150;
  271. elif size == 'small':
  272. size = 64;
  273. elif size == 'tiny':
  274. size = 46;
  275. else:
  276. size = 100
  277. return 'http://www.gravatar.com/avatar/%s?s=%s' % (hashlib.md5(self.email).hexdigest(), size)
  278. def email_user(self, request, template, subject, context={}):
  279. templates = request.theme.get_email_templates(template)
  280. context = RequestContext(request, context)
  281. context['author'] = context['user']
  282. context['user'] = self
  283. # Set message recipient
  284. if settings.DEBUG:
  285. recipient = settings.CATCH_ALL_EMAIL_ADDRESS
  286. else:
  287. recipient = self.email
  288. # Build and send message
  289. email = EmailMultiAlternatives(subject, templates[0].render(context), settings.EMAIL_HOST_USER, [recipient])
  290. email.attach_alternative(templates[1].render(context), "text/html")
  291. email.send()
  292. def get_date(self):
  293. return self.join_date
  294. class Guest(object):
  295. """
  296. Misago Guest dummy
  297. """
  298. def is_admin(self):
  299. return False
  300. def is_anonymous(self):
  301. return True
  302. def is_authenticated(self):
  303. return False
  304. def is_crawler(self):
  305. return False
  306. def is_banned(self):
  307. return False
  308. class Crawler(object):
  309. """
  310. Misago Crawler dummy
  311. """
  312. def __init__(self, username):
  313. self.username = username
  314. def is_admin(self):
  315. return False
  316. def is_anonymous(self):
  317. return True
  318. def is_authenticated(self):
  319. return False
  320. def is_crawler(self):
  321. return True
  322. def is_banned(self):
  323. return False
  324. class Group(models.Model):
  325. """
  326. Misago Users Group model
  327. """
  328. name = models.CharField(max_length=255)
  329. name_slug = models.SlugField(max_length=255)
  330. hidden = models.BooleanField(default=False)
  331. tab = models.CharField(max_length=255,null=True,blank=True)
  332. position = models.IntegerField(default=0)
  333. rank = models.ForeignKey('Rank',null=True,blank=True,db_index=True,on_delete=models.SET_NULL)
  334. special = models.BooleanField(default=False)
  335. class Rank(models.Model):
  336. """
  337. Misago User Rank
  338. Ranks are ready style/title pairs that are assigned to users either per group, admin action or user activity.
  339. """
  340. name = models.CharField(max_length=255,null=True,blank=True)
  341. style = models.CharField(max_length=255,null=True,blank=True)
  342. title = models.CharField(max_length=255,null=True,blank=True)
  343. special = models.BooleanField(default=False)
  344. order = models.IntegerField(default=0)
  345. criteria = models.CharField(max_length=255,default='')
  346. class Follower(models.Model):
  347. """
  348. Misago Users follow model
  349. """
  350. user = models.ForeignKey('User',related_name='+')
  351. target = models.ForeignKey('User')
  352. since = models.DateTimeField()
  353. class Foe(models.Model):
  354. """
  355. Misago Users foes model
  356. """
  357. user = models.ForeignKey('User')
  358. target = models.ForeignKey('User',related_name='+')
  359. since = models.DateTimeField()