PageRenderTime 40ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/forum/models.py

http://ddtcms.googlecode.com/
Python | 402 lines | 294 code | 53 blank | 55 comment | 31 complexity | eec3d7316dadf2a1fe8cd88373f662b3 MD5 | raw file
Possible License(s): BSD-3-Clause
  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,ThreadManager,PostManager
  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. master = models.CharField(_("Master"), max_length=100,default='admin')
  28. order = models.IntegerField(_("Order"), default=0)
  29. objects = ForumManager()
  30. def _get_forum_latest_post(self):
  31. """This gets the latest post for the forum"""
  32. if not hasattr(self, '__forum_latest_post'):
  33. try:
  34. self.__forum_latest_post = Post.objects.filter(thread__forum__pk=self.id).latest("time")
  35. except Post.DoesNotExist:
  36. self.__forum_latest_post = None
  37. return self.__forum_latest_post
  38. forum_latest_post = property(_get_forum_latest_post)
  39. def _recurse_for_parents_slug(self, forum_obj):
  40. #This is used for the urls
  41. p_list = []
  42. if forum_obj.parent_id:
  43. p = forum_obj.parent
  44. p_list.append(p.slug)
  45. more = self._recurse_for_parents_slug(p)
  46. p_list.extend(more)
  47. if forum_obj == self and p_list:
  48. p_list.reverse()
  49. return p_list
  50. def get_absolute_url(self):
  51. from django.core.urlresolvers import reverse
  52. p_list = self._recurse_for_parents_slug(self)
  53. p_list.append(self.slug)
  54. return '%s%s/' % (reverse('forum_index'), '/'.join (p_list))
  55. def _recurse_for_parents_name(self, forum_obj):
  56. #This is used for the visual display & save validation
  57. p_list = []
  58. if forum_obj.parent_id:
  59. p = forum_obj.parent
  60. p_list.append(p.title)
  61. more = self._recurse_for_parents_name(p)
  62. p_list.extend(more)
  63. if forum_obj == self and p_list:
  64. p_list.reverse()
  65. return p_list
  66. def get_separator(self):
  67. return ' » '
  68. def _parents_repr(self):
  69. p_list = self._recurse_for_parents_name(self)
  70. return self.get_separator().join(p_list)
  71. _parents_repr.short_description = _("Forum parents")
  72. def _recurse_for_parents_name_url(self, forum__obj):
  73. #Get all the absolute urls and names (for use in site navigation)
  74. p_list = []
  75. url_list = []
  76. if forum__obj.parent_id:
  77. p = forum__obj.parent
  78. p_list.append(p.title)
  79. url_list.append(p.get_absolute_url())
  80. more, url = self._recurse_for_parents_name_url(p)
  81. p_list.extend(more)
  82. url_list.extend(url)
  83. if forum__obj == self and p_list:
  84. p_list.reverse()
  85. url_list.reverse()
  86. return p_list, url_list
  87. def get_url_name(self):
  88. #Get a list of the url to display and the actual urls
  89. p_list, url_list = self._recurse_for_parents_name_url(self)
  90. p_list.append(self.title)
  91. url_list.append(self.get_absolute_url())
  92. return zip(p_list, url_list)
  93. def __unicode__(self):
  94. return u'%s' % self.title
  95. class Meta:
  96. ordering = [ 'parent__id', 'order']
  97. verbose_name = _('Forum')
  98. verbose_name_plural = _('Forums')
  99. def save(self, force_insert=False, force_update=False):
  100. p_list = self._recurse_for_parents_name(self)
  101. if (self.title) in p_list:
  102. raise validators.ValidationError(_("You must not save a forum in itself!"))
  103. super(Forum, self).save(force_insert, force_update)
  104. def _flatten(self, L):
  105. """
  106. Taken from a python newsgroup post
  107. """
  108. if type(L) != type([]): return [L]
  109. if L == []: return L
  110. return self._flatten(L[0]) + self._flatten(L[1:])
  111. def _recurse_for_children(self, node):
  112. children = []
  113. children.append(node)
  114. for child in node.child.all():
  115. children_list = self._recurse_for_children(child)
  116. children.append(children_list)
  117. return children
  118. def get_all_children(self):
  119. """
  120. Gets a list of all of the children forums.
  121. """
  122. children_list = self._recurse_for_children(self)
  123. flat_list = self._flatten(children_list[1:])
  124. return flat_list
  125. def get_children(self):
  126. """
  127. Gets a list of the children forums.
  128. """
  129. children_list = self.child.all()
  130. return children_list
  131. def has_children(self):
  132. if self.get_children():
  133. return True
  134. else:
  135. return False
  136. def get_posts_on_today(self):
  137. """
  138. Gets the posts of today.
  139. """
  140. posts=0
  141. if 1:
  142. today=datetime.datetime.now().date()
  143. posts=Post.objects.filter(thread__forum__pk=self.id,time__year=today.year,time__month=today.month,time__day=today.day).count()
  144. return posts
  145. def get_threads_on_today(self):
  146. """
  147. Gets the threads on today.
  148. """
  149. threads=0
  150. if 1:
  151. today=datetime.datetime.now().date()
  152. posts=thread.objects.filter(forum__pk=self.id,time__year=today.year,time__month=today.month,time__day=today.day).count()
  153. return posts
  154. def get_latest_post(self):
  155. """
  156. Gets the latest post of today.
  157. """
  158. try:
  159. the_latest_post = Post.objects.filter(thread__forum__pk=self.id).latest("time")
  160. except Post.DoesNotExist:
  161. the_latest_post = None
  162. return the_latest_post
  163. def get_master_list(self):
  164. masters=self.master.split(",")#['admin','huyoo']
  165. return masters
  166. def get_groups(self):
  167. groups=[]
  168. try:
  169. groups=self.groups.all().select_related()
  170. except:
  171. groups=[]
  172. return groups
  173. THREAD_STATUS_CHOICES= (
  174. ('NORMAL', _('NORMAL')),
  175. ('DELETED', _('DELETED')),
  176. ('LOCKED', _('LOCKED')),
  177. ('VERIFY', _('VERIFY')),
  178. ('PAYMONEY', _('PAYMONEY')),
  179. ('STICKYFORUM', _('STICKYFORUM')),
  180. ('STICKYPARENT', _('STICKYPARENT')),
  181. ('STICKYROOT', _('STICKYROOT')),
  182. )
  183. class Thread(models.Model):
  184. """
  185. A Thread belongs in a Forum, and is a collection of posts.
  186. Threads can be closed or stickied which alter their behaviour
  187. in the thread listings. Again, the posts & views fields are
  188. automatically updated with saving a post or viewing the thread.
  189. """
  190. forum = models.ForeignKey(Forum)
  191. author = models.ForeignKey(User, related_name='forum_thread_set')
  192. title = models.CharField(_("Title"), max_length=100)
  193. sticky = models.BooleanField(_("Sticky?"), blank=True, default=False)
  194. closed = models.BooleanField(_("Closed?"), blank=True, default=False)
  195. status = models.CharField(_("status?"), blank=True, null=True,max_length=10,choices=THREAD_STATUS_CHOICES,default="NORMAL")
  196. posts = models.IntegerField(_("Posts"), default=0)
  197. views = models.IntegerField(_("Views"), default=0)
  198. latest_post_time = models.DateTimeField(_("Latest Post Time"), blank=True, null=True)
  199. objects = ThreadManager()
  200. def _get_thread_latest_post(self):
  201. """This gets the latest post for the thread"""
  202. if not hasattr(self, '__thread_latest_post'):
  203. try:
  204. self.__thread_latest_post = Post.objects.filter(thread__pk=self.id).latest("time")
  205. except Post.DoesNotExist:
  206. self.__thread_latest_post = None
  207. return self.__thread_latest_post
  208. thread_latest_post = property(_get_thread_latest_post)
  209. class Meta:
  210. ordering = ('sticky', '-latest_post_time')
  211. verbose_name = _('Thread')
  212. verbose_name_plural = _('Threads')
  213. def save(self, force_insert=False, force_update=False):
  214. f = self.forum
  215. f.threads = f.thread_set.count()
  216. f.save()
  217. if not self.sticky:
  218. self.sticky = False
  219. super(Thread, self).save(force_insert, force_update)
  220. def delete(self):
  221. super(Thread, self).delete()
  222. f = self.forum
  223. f.threads = f.thread_set.count()
  224. f.posts = Post.objects.filter(thread__forum__pk=f.id).count()
  225. f.save()
  226. def get_absolute_url(self):
  227. return ('forum_view_thread', [str(self.id)])
  228. get_absolute_url = models.permalink(get_absolute_url)
  229. def __unicode__(self):
  230. return u'%s' % self.title
  231. def get_previous(self):
  232. try:
  233. # isnull is to check whether it's published or not - drafts don't have dates, apparently
  234. return Thread.objects.all().filter(id__lt=self.id,forum=self.forum)[0]
  235. except IndexError, e:
  236. # print 'Exception: %s' % e.message
  237. return None
  238. def get_next(self):
  239. try:
  240. # isnull is to check whether it's published or not - drafts don't have dates, apparently
  241. return Thread.objects.all().filter(id__gt=self.id,forum=self.forum).order_by('id')[0]
  242. except IndexError, e:
  243. # print 'Exception: %s' % e.message
  244. return None
  245. POST_STATUS_CHOICES= (
  246. ('NORMAL', _('NORMAL POST')),
  247. ('DELETED', _('DELETED POST')),
  248. ('SHIELD', _('SHIELD POST')),
  249. )
  250. class Post(models.Model):
  251. """
  252. A Post is a User's input to a thread. Uber-basic - the save()
  253. method also updates models further up the heirarchy (Thread,Forum)
  254. """
  255. thread = models.ForeignKey(Thread)
  256. author = models.ForeignKey(User, related_name='forum_post_set')
  257. body = models.TextField(_("Body"))
  258. time = models.DateTimeField(_("Posted Time,the time will be never changed"), blank=True, null=True)
  259. show_sign = models.BooleanField(_("Show Sign?"), blank=True, default=True)
  260. is_digest = models.IntegerField(_("Digest?"), blank=True, null=True,default=0)
  261. status = models.CharField(_("status?"), blank=True, null=True,max_length=10,choices=THREAD_STATUS_CHOICES,default="NORMAL")
  262. purchaser = models.ManyToManyField(User, blank=True, null=True)
  263. floor = models.IntegerField(_("floor number"), blank=True, null=True,default=0)
  264. objects = PostManager()
  265. def save(self, force_insert=False, force_update=False):
  266. new_post = False
  267. top_floor=1 # or 0 indexed?
  268. if not self.id:
  269. self.time = datetime.datetime.now()
  270. try:
  271. t1=self.thread
  272. latest_post=t1.post_set.latest('time') # get the latest one
  273. latest_post_floor=latest_post.floor # the thread already exist atleast another post,except this one
  274. # insert a post
  275. if self.floor == 0: # if floor == 0, the post is the first time insert to post set
  276. self.floor = latest_post_floor + 1
  277. # update a post
  278. #elseif floor !=0 :# update a exsist post, save the update data,do not need to update its floor number
  279. # self.floor already >= 1 ,so donot need change
  280. except Post.DoesNotExist: # the thread has no post return by the up progress(inserting)
  281. self.floor = top_floor # top floor is 1 indexed
  282. super(Post, self).save(force_insert, force_update)
  283. t = self.thread
  284. t.latest_post_time = t.post_set.latest('time').time
  285. t.posts = t.post_set.count()
  286. t.save()
  287. f = self.thread.forum
  288. f.threads = f.thread_set.count()
  289. f.posts = Post.objects.filter(thread__forum__pk=f.id).count()
  290. f.save()
  291. def delete(self):
  292. try:
  293. latest_post = Post.objects.exclude(pk=self.id).latest('time')
  294. latest_post_time = latest_post.time
  295. except Post.DoesNotExist:
  296. latest_post_time = None
  297. t = self.thread
  298. t.posts = t.post_set.exclude(pk=self.id).count()
  299. t.latest_post_time = latest_post_time
  300. t.save()
  301. f = self.thread.forum
  302. f.posts = Post.objects.filter(thread__forum__pk=f.id).exclude(pk=self.id).count()
  303. f.save()
  304. super(Post, self).delete()
  305. class Meta:
  306. ordering = ('-time',)
  307. verbose_name = _("Post")
  308. verbose_name_plural = _("Posts")
  309. def get_absolute_url(self):
  310. return '%s?page=last#post%s' % (self.thread.get_absolute_url(), self.id)
  311. def __unicode__(self):
  312. return u"%s#post%s" % (self.thread ,self.id)
  313. def has_attachment(self):
  314. return False
  315. def rebuild_floors(self):
  316. t = self.thread
  317. t.latest_post_time = t.post_set.latest('time').time
  318. return True
  319. def get_all_purchaser(self):
  320. pass
  321. def total_attachments(self):
  322. return Attachment.objects.attachments_for_object(self)
  323. class Subscription(models.Model):
  324. """
  325. Allow users to subscribe to threads.
  326. """
  327. author = models.ForeignKey(User)
  328. thread = models.ForeignKey(Thread)
  329. class Meta:
  330. unique_together = (("author", "thread"),)
  331. verbose_name = _('Subscription')
  332. verbose_name_plural = _('Subscriptions')
  333. def __unicode__(self):
  334. return u"%s to %s" % (self.author, self.thread)