forummodel.py 11 KB

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