PageRenderTime 58ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/dinette/models.py

https://github.com/agiliq/Dinette
Python | 375 lines | 372 code | 3 blank | 0 comment | 0 complexity | 1f0a4594688ccf8174da7774c40556ed MD5 | raw file
  1. from django.db import models
  2. from django.contrib.auth.models import User, Group
  3. from django.conf import settings
  4. from django.contrib.sites.models import Site
  5. from django.template.defaultfilters import slugify
  6. from django.db.models.signals import post_save
  7. from django.template.defaultfilters import truncatewords
  8. from django.core.urlresolvers import reverse
  9. import hashlib
  10. from BeautifulSoup import BeautifulSoup
  11. import datetime
  12. from dinette.libs.postmarkup import render_bbcode
  13. from markupfield.fields import MarkupField
  14. class SiteConfig(models.Model):
  15. name = models.CharField(max_length = 100)
  16. tag_line = models.TextField(max_length = 100)
  17. class SuperCategory(models.Model):
  18. name = models.CharField(max_length = 100)
  19. description = models.TextField(default='')
  20. ordering = models.PositiveIntegerField(default = 1)
  21. created_on = models.DateTimeField(auto_now_add=True)
  22. updated_on = models.DateTimeField(auto_now=True)
  23. posted_by = models.ForeignKey(User)
  24. accessgroups = models.ManyToManyField(Group, related_name='can_access_forums')
  25. class Meta:
  26. verbose_name = "Super Category"
  27. verbose_name_plural = "Super Categories"
  28. ordering = ('-ordering', 'created_on')
  29. def __unicode__(self):
  30. return self.name
  31. class Category(models.Model):
  32. name = models.CharField(max_length = 100)
  33. slug = models.SlugField(max_length = 110)
  34. description = models.TextField(default='')
  35. ordering = models.PositiveIntegerField(default = 1)
  36. super_category = models.ForeignKey(SuperCategory)
  37. created_on = models.DateTimeField(auto_now_add=True)
  38. updated_on = models.DateTimeField(auto_now=True)
  39. posted_by = models.ForeignKey(User, related_name='cposted')
  40. moderated_by = models.ManyToManyField(User, related_name='moderaters')
  41. class Meta:
  42. verbose_name = "Category"
  43. verbose_name_plural = "Categories"
  44. ordering = ('ordering','-created_on')
  45. def save(self, *args, **kwargs):
  46. if not self.slug:
  47. slug = slugify(self.name)
  48. same_slug_count = Category.objects.filter(slug__startswith = slug).count()
  49. if same_slug_count:
  50. slug = slug + str(same_slug_count)
  51. self.slug = slug
  52. super(Category, self).save(*args, **kwargs)
  53. def get_absolute_url(self):
  54. return reverse("dinette_index", args=[self.slug])
  55. def getCategoryString(self):
  56. return "category/%s" % self.slug
  57. def noofPosts(self):
  58. count = 0
  59. for topic in self.get_topics():
  60. #total posts for this topic = total replies + 1 (1 is for the topic as we are considering it as topic)
  61. count += topic.reply_set.count() + 1
  62. return count
  63. def lastPostDatetime(self):
  64. ''' we are assuming post can be topic / reply
  65. we are finding out the last post / (if exists) last reply datetime '''
  66. return self.lastPost().created_on
  67. def lastPostedUser(self):
  68. ''' we are assuming post can be topic / reply
  69. we are finding out the last post / (if exists) last reply datetime '''
  70. return self.lastPost().posted_by
  71. def lastPost(self):
  72. if(self.ftopics_set.count() == 0):
  73. return self
  74. obj = self.ftopics_set.order_by('-created_on')[0]
  75. if (obj.reply_set.count() > 0 ):
  76. return obj.reply_set.order_by("-created_on")[0]
  77. else :
  78. return obj
  79. def get_topics(self):
  80. return Ftopics.objects.filter(category=self)
  81. def __unicode__(self):
  82. return self.name
  83. class TopicManager(models.Manager):
  84. use_for_related_fields = True
  85. def get_queryset(self):
  86. return super(TopicManager, self).get_queryset().filter(is_hidden = False)
  87. def get_new_since(self, when):
  88. "Topics with new replies after @when"
  89. now = datetime.datetime.now()
  90. return self.filter(last_reply_on__gt = now)
  91. class Ftopics(models.Model):
  92. category = models.ForeignKey(Category)
  93. posted_by = models.ForeignKey(User)
  94. subject = models.CharField(max_length=999)
  95. slug = models.SlugField(max_length = 200, db_index = True)
  96. message = MarkupField(default_markup_type=getattr(settings,
  97. 'DEFAULT_MARKUP_TYPE',
  98. 'markdown'),
  99. markup_choices=settings.MARKUP_RENDERERS,
  100. escape_html=True,
  101. )
  102. file = models.FileField(upload_to='dinette/files',default='',null=True,blank=True)
  103. attachment_type = models.CharField(max_length=20,default='nofile')
  104. filename = models.CharField(max_length=100,default="dummyname.txt")
  105. viewcount = models.IntegerField(default=0)
  106. replies = models.IntegerField(default=0)
  107. created_on = models.DateTimeField(auto_now_add=True)
  108. updated_on = models.DateTimeField(auto_now=True)
  109. last_reply_on = models.DateTimeField(auto_now_add=True)
  110. num_replies = models.PositiveSmallIntegerField(default = 0)
  111. #Moderation features
  112. announcement_flag = models.BooleanField(default=False)
  113. is_closed = models.BooleanField(default=False)
  114. is_sticky = models.BooleanField(default=False)
  115. is_hidden = models.BooleanField(default=False)
  116. # use TopicManager as default, prevent leaking of hidden topics
  117. default = models.Manager()
  118. objects = TopicManager()
  119. # for topic subscriptions
  120. subscribers = models.ManyToManyField(User, related_name='subscribers')
  121. class Meta:
  122. ordering = ('-is_sticky', '-last_reply_on',)
  123. get_latest_by = ('created_on')
  124. verbose_name = "Topic"
  125. verbose_name_plural = "Topics"
  126. def save(self, *args, **kwargs):
  127. if not self.slug:
  128. slug = slugify(self.subject)
  129. slug = slug[:198]
  130. same_slug_count = Ftopics.objects.filter(slug__startswith = slug).count()
  131. if same_slug_count:
  132. slug = slug + str(same_slug_count)
  133. self.slug = slug
  134. super(Ftopics, self).save(*args, **kwargs)
  135. def __unicode__(self):
  136. return self.subject
  137. def get_absolute_url(self):
  138. return reverse('dinette_topic_detail', kwargs={'categoryslug':self.category.slug, 'topic_slug': self.slug})
  139. def htmlfrombbcode(self):
  140. if(len(self.message.raw.strip()) > 0):
  141. return render_bbcode(self.message.raw)
  142. else :
  143. return ""
  144. def search_snippet(self):
  145. msg = "%s %s"% (self.subject, self.message.rendered)
  146. return truncatewords(msg, 50)
  147. def getTopicString(self):
  148. #which is helpful for doing reverse lookup of an feed url for a topic
  149. return "topic/%s" % self.slug
  150. def lastPostDatetime(self):
  151. return self.lastPost().created_on
  152. def lastPostedUser(self):
  153. return self.lastPost().posted_by.username
  154. def lastPost(self):
  155. if (self.reply_set.count() == 0):
  156. return self
  157. return self.reply_set.order_by('-created_on')[0]
  158. def classname(self):
  159. return self.__class__.__name__
  160. class ReplyManager(models.Manager):
  161. use_for_related_fields = True
  162. def get_queryset(self):
  163. return super(ReplyManager, self).get_queryset().filter(topic__is_hidden=False)
  164. # Create Replies for a topic
  165. class Reply(models.Model):
  166. topic = models.ForeignKey(Ftopics)
  167. posted_by = models.ForeignKey(User)
  168. message = MarkupField(default_markup_type=getattr(settings,
  169. 'DEFAULT_MARKUP_TYPE',
  170. 'markdown'),
  171. markup_choices=settings.MARKUP_RENDERERS,
  172. escape_html=True,
  173. )
  174. file = models.FileField(upload_to='dinette/files',default='',null=True,blank=True)
  175. attachment_type = models.CharField(max_length=20,default='nofile')
  176. filename = models.CharField(max_length=100,default="dummyname.txt")
  177. reply_number = models.SmallIntegerField()
  178. created_on = models.DateTimeField(auto_now_add=True)
  179. updated_on = models.DateTimeField(auto_now=True)
  180. # replies for hidden topics should be hidden as well
  181. default = models.Manager()
  182. objects = ReplyManager()
  183. class Meta:
  184. verbose_name = "Reply"
  185. verbose_name_plural = "Replies"
  186. ordering = ('created_on',)
  187. get_latest_by = ('created_on', )
  188. def save(self, *args, **kwargs):
  189. if not self.pk:
  190. self.reply_number = self.topic.reply_set.all().count() + 1
  191. super(Reply, self).save(*args, **kwargs)
  192. def __unicode__(self):
  193. return truncatewords(self.message, 10)
  194. def search_snippet(self):
  195. msg = "%s %s"%(self.message.rendered, self.topic.subject)
  196. return truncatewords(msg, 100)
  197. @models.permalink
  198. def get_absolute_url(self):
  199. return ('dinette_topic_detail',(),{'categoryslug':self.topic.category.slug,'topic_slug': self.topic.slug})
  200. def get_url_with_fragment(self):
  201. page = (self.reply_number-1)/settings.REPLY_PAGE_SIZE + 1
  202. url = self.get_absolute_url()
  203. if not page == 1:
  204. return "%s?page=%s#%s" % (url, page, self.reply_number)
  205. else:
  206. return "%s#%s" % (url, self.reply_number)
  207. def htmlfrombbcode(self):
  208. soup = BeautifulSoup(self.message.raw)
  209. #remove all html tags from the message
  210. onlytext = ''.join(soup.findAll(text=True))
  211. #get the bbcode for the text
  212. if(len(onlytext.strip()) > 0):
  213. return render_bbcode(onlytext)
  214. else :
  215. return ""
  216. def classname(self):
  217. return self.__class__.__name__
  218. class DinetteUserProfile(models.Model):
  219. user = models.OneToOneField(User)
  220. last_activity = models.DateTimeField(auto_now_add=True)
  221. #When was the last session. Used in page activity since last session.
  222. last_session_activity = models.DateTimeField(auto_now_add=True)
  223. userrank = models.CharField(max_length=30, default="Junior Member")
  224. last_posttime = models.DateTimeField(auto_now_add=True)
  225. photo = models.ImageField(upload_to='dinette/files', null=True, blank=True)
  226. signature = models.CharField(max_length = 1000, null = True, blank = True)
  227. slug = models.SlugField(max_length=200, db_index=True, unique=True)
  228. is_subscribed_to_digest = models.BooleanField(default=False)
  229. def __unicode__(self):
  230. return self.user.username
  231. #Populate the user fields for easy access
  232. @property
  233. def username(self):
  234. return self.user.username
  235. @property
  236. def first_name(self):
  237. return self.user.first_name
  238. @property
  239. def last_name(self):
  240. return self.user.last_name
  241. def get_total_posts(self):
  242. print self.user.ftopics_set.count() + self.user.reply_set.count()
  243. return self.user.ftopics_set.count() + self.user.reply_set.count()
  244. def is_online(self):
  245. from django.conf import settings
  246. last_online_duration = getattr(settings, 'LAST_ONLINE_DURATION', 900)
  247. now = datetime.datetime.now()
  248. if (now - self.last_activity).seconds < last_online_duration:
  249. return True
  250. return False
  251. def getMD5(self):
  252. m = hashlib.md5()
  253. m.update(self.user.email)
  254. return m.hexdigest()
  255. def get_since_last_visit(self):
  256. "Topics with new relies since last visit"
  257. return Ftopics.objects.get_new_since(self.last_session_activity)
  258. @models.permalink
  259. def get_absolute_url(self):
  260. return ('dinette_user_profile', [self.slug])
  261. def save(self, *args, **kwargs):
  262. if not self.slug:
  263. slug = slugify(self.user.username)
  264. if slug:
  265. same_slug_count = self._default_manager.filter(slug__startswith=slug).count()
  266. if same_slug_count:
  267. slug = slug + str(same_slug_count)
  268. self.slug = slug
  269. else:
  270. #fallback to user id
  271. slug = self.user.id
  272. super(DinetteUserProfile, self).save(*args, **kwargs)
  273. class NavLink(models.Model):
  274. title = models.CharField(max_length = 100)
  275. url = models.URLField()
  276. class Meta:
  277. verbose_name = "Navigation Link"
  278. verbose_name_plural = "Navigation Links"
  279. def __unicode__(self):
  280. return self.title
  281. def create_user_profile(sender, instance, created, **kwargs):
  282. if created:
  283. DinetteUserProfile.objects.create(user=instance)
  284. def update_topic_on_reply(sender, instance, created, **kwargs):
  285. if created:
  286. instance.topic.last_reply_on = instance.created_on
  287. instance.topic.num_replies += 1
  288. instance.topic.save()
  289. def notify_subscribers_on_reply(sender, instance, created, **kwargs):
  290. if created:
  291. site = Site.objects.get_current()
  292. subject = "%s replied on %s" %(instance.posted_by, instance.topic.subject)
  293. body = instance.message.rendered
  294. from_email = getattr(settings, 'DINETTE_FROM_EMAIL', '%s notifications <admin@%s>' %(site.name, site.domain))
  295. # exclude the user who posted this, even if he is subscribed
  296. for subscriber in instance.topic.subscribers.exclude(username=instance.posted_by.username):
  297. subscriber.email_user(subject, body, from_email)
  298. post_save.connect(create_user_profile, sender=User)
  299. post_save.connect(update_topic_on_reply, sender=Reply)
  300. post_save.connect(notify_subscribers_on_reply, sender=Reply)