PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/forum/models.py

https://github.com/RockHoward/django-forum
Python | 264 lines | 193 code | 32 blank | 39 comment | 19 complexity | 7fbb5d23ebd88ca97e272f4f716a95e1 MD5 | raw file
  1. """
  2. A basic forum model with corresponding thread/post models.
  3. Just about all logic required for smooth updates is in the save()
  4. methods. A little extra logic is in views.py.
  5. """
  6. from django.db import models
  7. import datetime
  8. from django.contrib.auth.models import User, Group
  9. from django.conf import settings
  10. from django.utils.translation import ugettext_lazy as _
  11. from forum.managers import ForumManager
  12. class Forum(models.Model):
  13. """
  14. Very basic outline for a Forum, or group of threads. The threads
  15. and posts fielsd are updated by the save() methods of their
  16. respective models and are used for display purposes.
  17. All of the parent/child recursion code here is borrowed directly from
  18. the Satchmo project: http://www.satchmoproject.com/
  19. """
  20. groups = models.ManyToManyField(Group, blank=True)
  21. title = models.CharField(_("Title"), max_length=100)
  22. slug = models.SlugField(_("Slug"))
  23. parent = models.ForeignKey('self', blank=True, null=True, related_name='child')
  24. description = models.TextField(_("Description"))
  25. threads = models.IntegerField(_("Threads"), default=0)
  26. posts = models.IntegerField(_("Posts"), default=0)
  27. objects = ForumManager()
  28. def _get_forum_latest_post(self):
  29. """This gets the latest post for the forum"""
  30. if not hasattr(self, '__forum_latest_post'):
  31. try:
  32. self.__forum_latest_post = Post.objects.filter(thread__forum__pk=self.id).latest("time")
  33. except Post.DoesNotExist:
  34. self.__forum_latest_post = None
  35. return self.__forum_latest_post
  36. forum_latest_post = property(_get_forum_latest_post)
  37. def _recurse_for_parents_slug(self, forum_obj):
  38. #This is used for the urls
  39. p_list = []
  40. if forum_obj.parent_id:
  41. p = forum_obj.parent
  42. p_list.append(p.slug)
  43. more = self._recurse_for_parents_slug(p)
  44. p_list.extend(more)
  45. if forum_obj == self and p_list:
  46. p_list.reverse()
  47. return p_list
  48. def get_absolute_url(self):
  49. from django.core.urlresolvers import reverse
  50. p_list = self._recurse_for_parents_slug(self)
  51. p_list.append(self.slug)
  52. return '%s%s/' % (reverse('forum_index'), '/'.join (p_list))
  53. def _recurse_for_parents_name(self, forum_obj):
  54. #This is used for the visual display & save validation
  55. p_list = []
  56. if forum_obj.parent_id:
  57. p = forum_obj.parent
  58. p_list.append(p.title)
  59. more = self._recurse_for_parents_name(p)
  60. p_list.extend(more)
  61. if forum_obj == self and p_list:
  62. p_list.reverse()
  63. return p_list
  64. def get_separator(self):
  65. return ' » '
  66. def _parents_repr(self):
  67. p_list = self._recurse_for_parents_name(self)
  68. return self.get_separator().join(p_list)
  69. _parents_repr.short_description = _("Forum parents")
  70. def _recurse_for_parents_name_url(self, forum__obj):
  71. #Get all the absolute urls and names (for use in site navigation)
  72. p_list = []
  73. url_list = []
  74. if forum__obj.parent_id:
  75. p = forum__obj.parent
  76. p_list.append(p.title)
  77. url_list.append(p.get_absolute_url())
  78. more, url = self._recurse_for_parents_name_url(p)
  79. p_list.extend(more)
  80. url_list.extend(url)
  81. if forum__obj == self and p_list:
  82. p_list.reverse()
  83. url_list.reverse()
  84. return p_list, url_list
  85. def get_url_name(self):
  86. #Get a list of the url to display and the actual urls
  87. p_list, url_list = self._recurse_for_parents_name_url(self)
  88. p_list.append(self.title)
  89. url_list.append(self.get_absolute_url())
  90. return zip(p_list, url_list)
  91. def __unicode__(self):
  92. return u'%s' % self.title
  93. class Meta:
  94. ordering = ['title',]
  95. verbose_name = _('Forum')
  96. verbose_name_plural = _('Forums')
  97. def save(self, force_insert=False, force_update=False):
  98. p_list = self._recurse_for_parents_name(self)
  99. if (self.title) in p_list:
  100. raise validators.ValidationError(_("You must not save a forum in itself!"))
  101. super(Forum, self).save(force_insert, force_update)
  102. def _flatten(self, L):
  103. """
  104. Taken from a python newsgroup post
  105. """
  106. if type(L) != type([]): return [L]
  107. if L == []: return L
  108. return self._flatten(L[0]) + self._flatten(L[1:])
  109. def _recurse_for_children(self, node):
  110. children = []
  111. children.append(node)
  112. for child in node.child.all():
  113. children_list = self._recurse_for_children(child)
  114. children.append(children_list)
  115. return children
  116. def get_all_children(self):
  117. """
  118. Gets a list of all of the children forums.
  119. """
  120. children_list = self._recurse_for_children(self)
  121. flat_list = self._flatten(children_list[1:])
  122. return flat_list
  123. class Thread(models.Model):
  124. """
  125. A Thread belongs in a Forum, and is a collection of posts.
  126. Threads can be closed or stickied which alter their behaviour
  127. in the thread listings. Again, the posts & views fields are
  128. automatically updated with saving a post or viewing the thread.
  129. """
  130. forum = models.ForeignKey(Forum)
  131. title = models.CharField(_("Title"), max_length=100)
  132. sticky = models.BooleanField(_("Sticky?"), blank=True, null=True)
  133. closed = models.BooleanField(_("Closed?"), blank=True, null=True)
  134. posts = models.IntegerField(_("Posts"), default=0)
  135. views = models.IntegerField(_("Views"), default=0)
  136. latest_post_time = models.DateTimeField(_("Latest Post Time"), blank=True, null=True)
  137. def _get_thread_latest_post(self):
  138. """This gets the latest post for the thread"""
  139. if not hasattr(self, '__thread_latest_post'):
  140. try:
  141. self.__thread_latest_post = Post.objects.filter(thread__pk=self.id).latest("time")
  142. except Post.DoesNotExist:
  143. self.__thread_latest_post = None
  144. return self.__thread_latest_post
  145. thread_latest_post = property(_get_thread_latest_post)
  146. class Meta:
  147. ordering = ('-sticky', '-latest_post_time')
  148. verbose_name = _('Thread')
  149. verbose_name_plural = _('Threads')
  150. def save(self, force_insert=False, force_update=False):
  151. f = self.forum
  152. f.threads = f.thread_set.count()
  153. f.save()
  154. super(Thread, self).save(force_insert, force_update)
  155. def delete(self):
  156. super(Thread, self).delete()
  157. f = self.forum
  158. f.threads = f.thread_set.count()
  159. f.posts = Post.objects.filter(thread__forum__pk=f.id).count()
  160. f.save()
  161. def get_absolute_url(self):
  162. return ('forum_view_thread', [str(self.id)])
  163. get_absolute_url = models.permalink(get_absolute_url)
  164. def __unicode__(self):
  165. return u'%s' % self.title
  166. class Post(models.Model):
  167. """
  168. A Post is a User's input to a thread. Uber-basic - the save()
  169. method also updates models further up the heirarchy (Thread,Forum)
  170. """
  171. thread = models.ForeignKey(Thread)
  172. author = models.ForeignKey(User, related_name='forum_post_set')
  173. body = models.TextField(_("Body"))
  174. time = models.DateTimeField(_("Time"), blank=True, null=True)
  175. def save(self, force_insert=False, force_update=False):
  176. new_post = False
  177. if not self.id:
  178. self.time = datetime.datetime.now()
  179. super(Post, self).save(force_insert, force_update)
  180. t = self.thread
  181. t.latest_post_time = t.post_set.latest('time').time
  182. t.posts = t.post_set.count()
  183. t.save()
  184. f = self.thread.forum
  185. f.threads = f.thread_set.count()
  186. f.posts = Post.objects.filter(thread__forum__pk=f.id).count()
  187. f.save()
  188. def delete(self):
  189. try:
  190. latest_post = Post.objects.exclude(pk=self.id).latest('time')
  191. latest_post_time = latest_post.time
  192. except Post.DoesNotExist:
  193. latest_post_time = None
  194. t = self.thread
  195. t.posts = t.post_set.exclude(pk=self.id).count()
  196. t.latest_post_time = latest_post_time
  197. t.save()
  198. f = self.thread.forum
  199. f.posts = Post.objects.filter(thread__forum__pk=f.id).exclude(pk=self.id).count()
  200. f.save()
  201. super(Post, self).delete()
  202. class Meta:
  203. ordering = ('-time',)
  204. def get_absolute_url(self):
  205. return '%s#post%s' % (self.thread.get_absolute_url(), self.id)
  206. def __unicode__(self):
  207. return u"%s" % self.id
  208. class Subscription(models.Model):
  209. """
  210. Allow users to subscribe to threads.
  211. """
  212. author = models.ForeignKey(User)
  213. thread = models.ForeignKey(Thread)
  214. class Meta:
  215. unique_together = (("author", "thread"),)
  216. verbose_name = _('Subscription')
  217. verbose_name_plural = _('Subscriptions')
  218. def __unicode__(self):
  219. return u"%s to %s" % (self.author, self.thread)