PageRenderTime 41ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/forum/models.py

https://github.com/open-machine-learning/mldata
Python | 405 lines | 383 code | 4 blank | 18 comment | 0 complexity | f8e6c0a8faf46b2fd869d0f1b2f15a7e 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. import datetime
  7. from django.db import models
  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 django.utils.html import escape
  12. from forum.managers import ForumManager
  13. from utils.markdown import markdown
  14. class Forum(models.Model):
  15. """Very basic outline for a Forum, or group of threads.
  16. The threads and posts fields are updated by the save() methods of their
  17. respective models and are used for display purposes.
  18. All of the parent/child recursion code here is borrowed directly from
  19. the Satchmo project: http://www.satchmoproject.com/
  20. @cvar groups: User groups this forum belongs to
  21. @type groups: list of Group / models.ManyToManyField
  22. @cvar title: forum's title
  23. @type title: string / models.CharField
  24. @cvar slug: forum's slug
  25. @type slug: string / models.CharField
  26. @cvar parent: parent forum
  27. @type parent: Forum / models.ForeignKey
  28. @cvar description: forum's description
  29. @type description: string / models.CharField
  30. @cvar threads: number of threads in forum
  31. @type threads: integer / models.IntegerField
  32. @cvar posts: number of posts in forum
  33. @type posts: integer / models.IntegerField
  34. @cvar ordering: ordering of forum
  35. @type ordering: integer / models.IntegerField
  36. @cvar objects: available objects managed by ForumManager
  37. @type objects: ForumManager
  38. """
  39. groups = models.ManyToManyField(Group, blank=True)
  40. title = models.CharField(_("Title"), max_length=100)
  41. slug = models.SlugField(_("Slug"))
  42. parent = models.ForeignKey('self', blank=True, null=True, related_name='child')
  43. description = models.TextField(_("Description"))
  44. threads = models.IntegerField(_("Threads"), default=0, editable=False)
  45. posts = models.IntegerField(_("Posts"), default=0, editable=False)
  46. ordering = models.IntegerField(_("Ordering"), blank=True, null=True)
  47. objects = ForumManager()
  48. def _get_forum_latest_post(self):
  49. """This gets the latest post for the forum.
  50. @return: latest forum post
  51. @rtype: Post or None
  52. """
  53. if not hasattr(self, '__forum_latest_post'):
  54. try:
  55. self.__forum_latest_post = Post.objects.filter(thread__forum__pk=self.id).latest("time")
  56. except Post.DoesNotExist:
  57. self.__forum_latest_post = None
  58. return self.__forum_latest_post
  59. forum_latest_post = property(_get_forum_latest_post)
  60. def _recurse_for_parents_slug(self, forum_obj):
  61. """This is used for the URLs.
  62. @param forum_obj: forum to recurse through
  63. @type forum_obj: Forum
  64. @return: forum slugs
  65. @rtype: list of strings
  66. """
  67. p_list = []
  68. if forum_obj.parent_id:
  69. p = forum_obj.parent
  70. p_list.append(p.slug)
  71. more = self._recurse_for_parents_slug(p)
  72. p_list.extend(more)
  73. if forum_obj == self and p_list:
  74. p_list.reverse()
  75. return p_list
  76. def get_absolute_url(self):
  77. from django.core.urlresolvers import reverse
  78. p_list = self._recurse_for_parents_slug(self)
  79. p_list.append(self.slug)
  80. return '%s%s/' % (reverse('forum_index'), '/'.join (p_list))
  81. def _recurse_for_parents_name(self, forum_obj):
  82. """This is used for the visual display & save validation.
  83. @param forum_obj: forum to recurse through
  84. @type forum_obj: Forum
  85. @return: forum slugs
  86. @rtype: list of strings
  87. """
  88. p_list = []
  89. if forum_obj.parent_id:
  90. p = forum_obj.parent
  91. p_list.append(p.title)
  92. more = self._recurse_for_parents_name(p)
  93. p_list.extend(more)
  94. if forum_obj == self and p_list:
  95. p_list.reverse()
  96. return p_list
  97. def get_separator(self):
  98. """Get forum seperator.
  99. @return: an HTML seperator
  100. @rtype: string
  101. """
  102. return ' » '
  103. def _parents_repr(self):
  104. """Get parents short description.
  105. @return: parents' names, joined by sepertor
  106. @rtype: string
  107. """
  108. p_list = self._recurse_for_parents_name(self)
  109. return self.get_separator().join(p_list)
  110. _parents_repr.short_description = _("Forum parents")
  111. def _recurse_for_parents_name_url(self, forum_obj):
  112. """Get all the absolute urls and names (for use in site navigation)
  113. @param forum_obj: forum to recurse through
  114. @type forum_obj: Forum
  115. @return: forum slugs
  116. @rtype: list of strings
  117. """
  118. p_list = []
  119. url_list = []
  120. if forum_obj.parent_id:
  121. p = forum_obj.parent
  122. p_list.append(p.title)
  123. url_list.append(p.get_absolute_url())
  124. more, url = self._recurse_for_parents_name_url(p)
  125. p_list.extend(more)
  126. url_list.extend(url)
  127. if forum_obj == self and p_list:
  128. p_list.reverse()
  129. url_list.reverse()
  130. return p_list, url_list
  131. def get_url_name(self):
  132. """Get a list of the url to display and the actual urls.
  133. @return: a list of URLs
  134. @rtype: list of strings
  135. """
  136. p_list, url_list = self._recurse_for_parents_name_url(self)
  137. p_list.append(self.title)
  138. url_list.append(self.get_absolute_url())
  139. return zip(p_list, url_list)
  140. def __unicode__(self):
  141. return u'%s' % self.title
  142. class Meta:
  143. ordering = ['ordering', 'title',]
  144. verbose_name = _('Forum')
  145. verbose_name_plural = _('Forums')
  146. def save(self, force_insert=False, force_update=False):
  147. p_list = self._recurse_for_parents_name(self)
  148. if (self.title) in p_list:
  149. raise validators.ValidationError(_("You must not save a forum in itself!"))
  150. super(Forum, self).save(force_insert, force_update)
  151. def _flatten(self, L):
  152. """Taken from a python newsgroup post to flatten a list.
  153. @param L: list to flatten
  154. @type L: list
  155. @return: flattened list
  156. @rtype: list
  157. """
  158. if type(L) != type([]): return [L]
  159. if L == []: return L
  160. return self._flatten(L[0]) + self._flatten(L[1:])
  161. def _recurse_for_children(self, node):
  162. """Get all children for given node.
  163. @param node: node to find children of
  164. @type node: generic
  165. @return: list of children nodes
  166. @rtype: generic
  167. """
  168. children = []
  169. children.append(node)
  170. for child in node.child.all():
  171. children_list = self._recurse_for_children(child)
  172. children.append(children_list)
  173. return children
  174. def get_all_children(self):
  175. """Gets a list of all of the child forums.
  176. @return: list of all child forums.
  177. @rtype: list of Forum
  178. """
  179. children_list = self._recurse_for_children(self)
  180. flat_list = self._flatten(children_list[1:])
  181. return flat_list
  182. class Thread(models.Model):
  183. """A Thread belongs in a Forum, and is a collection of posts.
  184. Threads can be closed or stickied which alter their behaviour
  185. in the thread listings. Again, the posts & views fields are
  186. automatically updated with saving a post or viewing the thread.
  187. @cvar forum: forum this thread belongs to
  188. @type forum: Forum / models.ForeignKey
  189. @cvar title: thread's title
  190. @type title: string / models.CharField
  191. @cvar sticky: if thread is sticky
  192. @type sticky: boolean / models.BooleanField
  193. @cvar closed: if thread is closed
  194. @type closed: boolean / models.BooleanField
  195. @cvar posts: number of posts in thread
  196. @type posts: integer / models.IntegerField
  197. @cvar views: number of thread views
  198. @type views: integer / models.IntegerField
  199. @cvar latest_post_time: when latest post was made
  200. @type latest_post_time: datetime / models.DateTimeField
  201. """
  202. forum = models.ForeignKey(Forum)
  203. title = models.CharField(_("Title"), max_length=100)
  204. sticky = models.BooleanField(_("Sticky?"), blank=True, default=False)
  205. closed = models.BooleanField(_("Closed?"), blank=True, default=False)
  206. posts = models.IntegerField(_("Posts"), default=0)
  207. views = models.IntegerField(_("Views"), default=0)
  208. latest_post_time = models.DateTimeField(_("Latest Post Time"), blank=True, null=True)
  209. def _get_thread_latest_post(self):
  210. """This gets the latest post for the thread.
  211. @return: latest thread post or none
  212. @rtype: Post
  213. """
  214. if not hasattr(self, '__thread_latest_post'):
  215. try:
  216. self.__thread_latest_post = Post.objects.filter(thread__pk=self.id).latest("time")
  217. except Post.DoesNotExist:
  218. self.__thread_latest_post = None
  219. return self.__thread_latest_post
  220. thread_latest_post = property(_get_thread_latest_post)
  221. class Meta:
  222. ordering = ('-sticky', '-latest_post_time')
  223. verbose_name = _('Thread')
  224. verbose_name_plural = _('Threads')
  225. def save(self, force_insert=False, force_update=False):
  226. f = self.forum
  227. f.threads = f.thread_set.count()
  228. f.save()
  229. if not self.sticky:
  230. self.sticky = False
  231. super(Thread, self).save(force_insert, force_update)
  232. def delete(self):
  233. super(Thread, self).delete()
  234. f = self.forum
  235. f.threads = f.thread_set.count()
  236. f.posts = Post.objects.filter(thread__forum__pk=f.id).count()
  237. f.save()
  238. def get_absolute_url(self):
  239. return ('forum_view_thread', [str(self.id)])
  240. get_absolute_url = models.permalink(get_absolute_url)
  241. def __unicode__(self):
  242. return u'%s' % self.title
  243. class Post(models.Model):
  244. """A Post is a User's input to a thread.
  245. Uber-basic - the save() method also updates models further up the
  246. hierarchy (Thread, Forum)
  247. @cvar thread: thread this post belongs to
  248. @type thread: Thread / models.ForeignKey
  249. @cvar author: post's author
  250. @type author: Django User / models.ForeignKey
  251. @cvar body: post's body
  252. @type body: string / models.TextField
  253. @cvar body_html: HTML version of post's body
  254. @type body_html: string / models.TextField
  255. @cvar time: time when post was made
  256. @type time: datetime / models.DateTimeField
  257. """
  258. thread = models.ForeignKey(Thread)
  259. author = models.ForeignKey(User, related_name='forum_post_set')
  260. body = models.TextField(_("Body"))
  261. body_html = models.TextField(editable=False)
  262. time = models.DateTimeField(_("Time"), blank=True, null=True)
  263. def save(self, force_insert=False, force_update=False):
  264. if not self.id:
  265. self.time = datetime.datetime.now()
  266. self.body_html = markdown(escape(self.body))
  267. super(Post, self).save(force_insert, force_update)
  268. t = self.thread
  269. t.latest_post_time = t.post_set.latest('time').time
  270. t.posts = t.post_set.count()
  271. t.save()
  272. f = self.thread.forum
  273. f.threads = f.thread_set.count()
  274. f.posts = Post.objects.filter(thread__forum__pk=f.id).count()
  275. f.save()
  276. def delete(self):
  277. try:
  278. latest_post = Post.objects.exclude(pk=self.id).latest('time')
  279. latest_post_time = latest_post.time
  280. except Post.DoesNotExist:
  281. latest_post_time = None
  282. t = self.thread
  283. t.posts = t.post_set.exclude(pk=self.id).count()
  284. t.latest_post_time = latest_post_time
  285. t.save()
  286. f = self.thread.forum
  287. f.posts = Post.objects.filter(thread__forum__pk=f.id).exclude(pk=self.id).count()
  288. f.save()
  289. super(Post, self).delete()
  290. class Meta:
  291. ordering = ('-time',)
  292. verbose_name = _('Post')
  293. verbose_name_plural = _('Posts')
  294. def get_absolute_url(self):
  295. return '%s?page=last#post%s' % (self.thread.get_absolute_url(), self.id)
  296. def __unicode__(self):
  297. return u"%s" % self.id
  298. class Subscription(models.Model):
  299. """Allow users to subscribe to threads.
  300. @cvar author: thread's author
  301. @type author: Django User / models.ForeignKey
  302. @cvar thread: subscribed thread
  303. @type thread: Thread / models.ForeignKey
  304. """
  305. author = models.ForeignKey(User)
  306. thread = models.ForeignKey(Thread)
  307. class Meta:
  308. unique_together = (("author", "thread"),)
  309. verbose_name = _('Subscription')
  310. verbose_name_plural = _('Subscriptions')
  311. def __unicode__(self):
  312. return u"%s to %s" % (self.author, self.thread)