forummodel.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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(pk) and
  109. acl.threads.acl[pk]['can_read_threads']):
  110. readable.append(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 delete(self, *args, **kwargs):
  145. delete_forum_content.send(sender=self)
  146. super(Forum, self).delete(*args, **kwargs)
  147. def __unicode__(self):
  148. if self.special == 'private_threads':
  149. return unicode(_('Private Threads'))
  150. if self.special == 'reports':
  151. return unicode(_('Reports'))
  152. if self.special == 'root':
  153. return unicode(_('Root Category'))
  154. return unicode(self.name)
  155. def set_description(self, description):
  156. self.description = description.strip()
  157. self.description_preparsed = ''
  158. if self.description:
  159. import markdown
  160. self.description_preparsed = markdown.markdown(description, safe_mode='escape', output_format=settings.OUTPUT_FORMAT)
  161. def copy_permissions(self, target):
  162. if target.pk != self.pk:
  163. from misago.models import Role
  164. for role in Role.objects.all():
  165. perms = role.permissions
  166. try:
  167. perms['forums'][self.pk] = perms['forums'][target.pk]
  168. role.permissions = perms
  169. role.save(force_update=True)
  170. except KeyError:
  171. pass
  172. def move_content(self, target):
  173. move_forum_content.send(sender=self, move_to=target)
  174. def attr(self, att):
  175. if self.attrs:
  176. return att in self.attrs.split()
  177. return False
  178. def new_last_thread(self, thread):
  179. self.last_thread = thread
  180. self.last_thread_name = thread.name
  181. self.last_thread_slug = thread.slug
  182. self.last_thread_date = thread.last
  183. self.last_poster = thread.last_poster
  184. self.last_poster_name = thread.last_poster_name
  185. self.last_poster_slug = thread.last_poster_slug
  186. self.last_poster_style = thread.last_poster_style
  187. def sync(self):
  188. self.threads = self.thread_set.filter(moderated=False).filter(deleted=False).count()
  189. self.posts = self.post_set.filter(moderated=False).count()
  190. self.last_poster = None
  191. self.last_poster_name = None
  192. self.last_poster_slug = None
  193. self.last_poster_style = None
  194. self.last_thread = None
  195. self.last_thread_date = None
  196. self.last_thread_name = None
  197. self.last_thread_slug = None
  198. try:
  199. last_thread = self.thread_set.filter(moderated=False).filter(deleted=False).order_by('-last').all()[0:][0]
  200. self.last_poster_name = last_thread.last_poster_name
  201. self.last_poster_slug = last_thread.last_poster_slug
  202. self.last_poster_style = last_thread.last_poster_style
  203. if last_thread.last_poster:
  204. self.last_poster = last_thread.last_poster
  205. self.last_thread = last_thread
  206. self.last_thread_date = last_thread.last
  207. self.last_thread_name = last_thread.name
  208. self.last_thread_slug = last_thread.slug
  209. except (IndexError, AttributeError):
  210. pass
  211. def prune(self):
  212. pass
  213. """
  214. Signals
  215. """
  216. def rename_user_handler(sender, **kwargs):
  217. Forum.objects.filter(last_poster=sender).update(
  218. last_poster_name=sender.username,
  219. last_poster_slug=sender.username_slug,
  220. )
  221. rename_user.connect(rename_user_handler, dispatch_uid='rename_forums_last_poster')