forummodel.py 11 KB

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