forummodel.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import urlparse
  2. from mptt.managers import TreeManager
  3. from mptt.models import MPTTModel, TreeForeignKey
  4. from django.conf import settings
  5. from django.core.cache import cache
  6. from django.db import models
  7. from django.utils.translation import ugettext_lazy as _
  8. from misago.signals import delete_forum_content, move_forum_content, rename_user
  9. class ForumManager(TreeManager):
  10. forums_tree = None
  11. def special_pk(self, name):
  12. self.populate_tree()
  13. return self.forums_tree[name].pk
  14. def special_model(self, name):
  15. self.populate_tree()
  16. return self.forums_tree[name]
  17. def populate_tree(self, force=False):
  18. if not self.forums_tree:
  19. self.forums_tree = cache.get('forums_tree')
  20. if not self.forums_tree or force:
  21. self.forums_tree = {}
  22. for forum in Forum.objects.order_by('lft'):
  23. self.forums_tree[forum.pk] = forum
  24. if forum.special:
  25. self.forums_tree[forum.special] = forum
  26. cache.set('forums_tree', self.forums_tree)
  27. def forum_parents(self, forum, include_self=False):
  28. self.populate_tree()
  29. parents = []
  30. parent = self.forums_tree[forum]
  31. if include_self:
  32. parents.append(parent)
  33. while parent.level > 1:
  34. parent = self.forums_tree[parent.parent_id]
  35. parents.append(parent)
  36. result = []
  37. for i in reversed(parents):
  38. result.append(i)
  39. return list(result)
  40. def parents_aware_forum(self, forum):
  41. self.populate_tree()
  42. proxy = Forum()
  43. try:
  44. proxy.id = forum.pk
  45. proxy.pk = forum.pk
  46. except AttributeError:
  47. proxy.id = forum
  48. proxy.pk = forum
  49. proxy.closed = False
  50. for parent in self.forum_parents(proxy.pk):
  51. if parent.closed:
  52. proxy.closed = True
  53. return proxy
  54. return proxy
  55. def treelist(self, acl, parent=None, tracker=None):
  56. complete_list = []
  57. forums_list = []
  58. parents = {}
  59. if parent:
  60. queryset = Forum.objects.filter(pk__in=acl.known_forums).filter(lft__gt=parent.lft).filter(rght__lt=parent.rght).order_by('lft')
  61. else:
  62. queryset = Forum.objects.filter(pk__in=acl.known_forums).order_by('lft')
  63. for forum in queryset:
  64. forum.subforums = []
  65. forum.is_read = False
  66. if tracker:
  67. forum.is_read = tracker.is_read(forum)
  68. parents[forum.pk] = forum
  69. complete_list.append(forum)
  70. if forum.parent_id in parents:
  71. parents[forum.parent_id].subforums.append(forum)
  72. else:
  73. forums_list.append(forum)
  74. # Second iteration - sum up forum counters
  75. for forum in reversed(complete_list):
  76. if forum.parent_id in parents and parents[forum.parent_id].type != 'redirect':
  77. parents[forum.parent_id].threads += forum.threads
  78. parents[forum.parent_id].posts += forum.posts
  79. if acl.can_browse(forum.pk):
  80. # If forum is unread, make parent unread too
  81. if not forum.is_read:
  82. parents[forum.parent_id].is_read = False
  83. # Sum stats
  84. if forum.last_thread_date and (not parents[forum.parent_id].last_thread_date or forum.last_thread_date > parents[forum.parent_id].last_thread_date):
  85. parents[forum.parent_id].last_thread_id = forum.last_thread_id
  86. parents[forum.parent_id].last_thread_name = forum.last_thread_name
  87. parents[forum.parent_id].last_thread_slug = forum.last_thread_slug
  88. parents[forum.parent_id].last_thread_date = forum.last_thread_date
  89. parents[forum.parent_id].last_poster_id = forum.last_poster_id
  90. parents[forum.parent_id].last_poster_name = forum.last_poster_name
  91. parents[forum.parent_id].last_poster_slug = forum.last_poster_slug
  92. parents[forum.parent_id].last_poster_style = forum.last_poster_style
  93. return forums_list
  94. def ignored_users(self, user, forums):
  95. check_ids = []
  96. for forum in forums:
  97. forum.last_poster_ignored = False
  98. if user.is_authenticated() and user.pk != forum.last_poster_id and forum.last_poster_id and not forum.last_poster_id in check_ids:
  99. check_ids.append(forum.last_poster_id)
  100. ignored_ids = []
  101. if check_ids and user.is_authenticated():
  102. for user in user.ignores.filter(id__in=check_ids).values('id'):
  103. ignored_ids.append(user['id'])
  104. def readable_forums(self, acl, include_special=False):
  105. self.populate_tree()
  106. readable = []
  107. for pk, forum in self.forums_tree.items():
  108. if ((include_special or not forum.special) and
  109. acl.forums.can_browse(forum.pk) and
  110. acl.threads.acl[forum.pk]['can_read_threads']):
  111. readable.append(forum.pk)
  112. return readable
  113. class Forum(MPTTModel):
  114. parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
  115. type = models.CharField(max_length=12)
  116. special = models.CharField(max_length=255, null=True, blank=True)
  117. name = models.CharField(max_length=255)
  118. slug = models.SlugField(max_length=255)
  119. description = models.TextField(null=True, blank=True)
  120. description_preparsed = models.TextField(null=True, blank=True)
  121. threads = models.PositiveIntegerField(default=0)
  122. threads_delta = models.PositiveIntegerField(default=0)
  123. posts = models.PositiveIntegerField(default=0)
  124. posts_delta = models.IntegerField(default=0)
  125. redirects = models.PositiveIntegerField(default=0)
  126. redirects_delta = models.IntegerField(default=0)
  127. last_thread = models.ForeignKey('Thread', related_name='+', null=True, blank=True, on_delete=models.SET_NULL)
  128. last_thread_name = models.CharField(max_length=255, null=True, blank=True)
  129. last_thread_slug = models.SlugField(max_length=255, null=True, blank=True)
  130. last_thread_date = models.DateTimeField(null=True, blank=True)
  131. last_poster = models.ForeignKey('User', related_name='+', null=True, blank=True, on_delete=models.SET_NULL)
  132. last_poster_name = models.CharField(max_length=255, null=True, blank=True)
  133. last_poster_slug = models.SlugField(max_length=255, null=True, blank=True)
  134. last_poster_style = models.CharField(max_length=255, null=True, blank=True)
  135. prune_start = models.PositiveIntegerField(default=0)
  136. prune_last = models.PositiveIntegerField(default=0)
  137. redirect = models.CharField(max_length=255, null=True, blank=True)
  138. attrs = models.CharField(max_length=255, null=True, blank=True)
  139. show_details = models.BooleanField(default=True)
  140. style = models.CharField(max_length=255, null=True, blank=True)
  141. closed = models.BooleanField(default=False)
  142. objects = ForumManager()
  143. class Meta:
  144. app_label = 'misago'
  145. def save(self, *args, **kwargs):
  146. super(Forum, self).save(*args, **kwargs)
  147. cache.delete('forums_tree')
  148. def delete(self, *args, **kwargs):
  149. delete_forum_content.send(sender=self)
  150. super(Forum, self).delete(*args, **kwargs)
  151. cache.delete('forums_tree')
  152. def __unicode__(self):
  153. if self.special == 'private_threads':
  154. return unicode(_('Private Threads'))
  155. if self.special == 'reports':
  156. return unicode(_('Reports'))
  157. if self.special == 'root':
  158. return unicode(_('Root Category'))
  159. return unicode(self.name)
  160. def set_description(self, description):
  161. self.description = description.strip()
  162. self.description_preparsed = ''
  163. if self.description:
  164. import markdown
  165. self.description_preparsed = markdown.markdown(description, safe_mode='escape', output_format=settings.OUTPUT_FORMAT)
  166. def copy_permissions(self, target):
  167. if target.pk != self.pk:
  168. from misago.models import Role
  169. for role in Role.objects.all():
  170. perms = role.permissions
  171. try:
  172. perms['forums'][self.pk] = perms['forums'][target.pk]
  173. role.permissions = perms
  174. role.save(force_update=True)
  175. except KeyError:
  176. pass
  177. def move_content(self, target):
  178. move_forum_content.send(sender=self, move_to=target)
  179. def attr(self, att):
  180. if self.attrs:
  181. return att in self.attrs.split()
  182. return False
  183. def redirect_domain(self):
  184. hostname = urlparse.urlparse(self.redirect).hostname
  185. scheme = urlparse.urlparse(self.redirect).scheme
  186. if scheme:
  187. scheme = '%s://' % scheme
  188. return '%s%s' % (scheme, hostname)
  189. def new_last_thread(self, thread):
  190. self.last_thread = thread
  191. self.last_thread_name = thread.name
  192. self.last_thread_slug = thread.slug
  193. self.last_thread_date = thread.last
  194. self.last_poster = thread.last_poster
  195. self.last_poster_name = thread.last_poster_name
  196. self.last_poster_slug = thread.last_poster_slug
  197. self.last_poster_style = thread.last_poster_style
  198. def sync(self):
  199. self.threads = self.thread_set.filter(moderated=False).filter(deleted=False).count()
  200. self.posts = self.post_set.filter(moderated=False).count()
  201. self.last_poster = None
  202. self.last_poster_name = None
  203. self.last_poster_slug = None
  204. self.last_poster_style = None
  205. self.last_thread = None
  206. self.last_thread_date = None
  207. self.last_thread_name = None
  208. self.last_thread_slug = None
  209. try:
  210. last_thread = self.thread_set.filter(moderated=False).filter(deleted=False).order_by('-last').all()[0:][0]
  211. self.last_poster_name = last_thread.last_poster_name
  212. self.last_poster_slug = last_thread.last_poster_slug
  213. self.last_poster_style = last_thread.last_poster_style
  214. if last_thread.last_poster:
  215. self.last_poster = last_thread.last_poster
  216. self.last_thread = last_thread
  217. self.last_thread_date = last_thread.last
  218. self.last_thread_name = last_thread.name
  219. self.last_thread_slug = last_thread.slug
  220. except (IndexError, AttributeError):
  221. pass
  222. def prune(self):
  223. pass
  224. """
  225. Signals
  226. """
  227. def rename_user_handler(sender, **kwargs):
  228. Forum.objects.filter(last_poster=sender).update(
  229. last_poster_name=sender.username,
  230. last_poster_slug=sender.username_slug,
  231. )
  232. rename_user.connect(rename_user_handler, dispatch_uid='rename_forums_last_poster')