PageRenderTime 34ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/askbot/models/content.py

https://github.com/chandankumar2199/askbot-devel
Python | 398 lines | 354 code | 17 blank | 27 comment | 4 complexity | ee61ce8ac73b0900d056b94d078cb486 MD5 | raw file
  1. import datetime
  2. from django.contrib.auth.models import User
  3. from django.contrib.contenttypes import generic
  4. from django.contrib.contenttypes.models import ContentType
  5. from django.db import models
  6. from django.utils import html as html_utils
  7. from django.utils.datastructures import SortedDict
  8. from askbot import const
  9. from askbot.models.meta import Comment, Vote
  10. from askbot.models.user import EmailFeedSetting
  11. from askbot.models.tag import Tag, MarkedTag, tags_match_some_wildcard
  12. from askbot.conf import settings as askbot_settings
  13. class Content(models.Model):
  14. """
  15. Base class for Question and Answer
  16. """
  17. author = models.ForeignKey(User, related_name='%(class)ss')
  18. added_at = models.DateTimeField(default=datetime.datetime.now)
  19. wiki = models.BooleanField(default=False)
  20. wikified_at = models.DateTimeField(null=True, blank=True)
  21. locked = models.BooleanField(default=False)
  22. locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_%(class)ss')
  23. locked_at = models.DateTimeField(null=True, blank=True)
  24. score = models.IntegerField(default=0)
  25. vote_up_count = models.IntegerField(default=0)
  26. vote_down_count = models.IntegerField(default=0)
  27. comment_count = models.PositiveIntegerField(default=0)
  28. offensive_flag_count = models.SmallIntegerField(default=0)
  29. last_edited_at = models.DateTimeField(null=True, blank=True)
  30. last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_%(class)ss')
  31. html = models.TextField(null=True)#html rendition of the latest revision
  32. text = models.TextField(null=True)#denormalized copy of latest revision
  33. comments = generic.GenericRelation(Comment)
  34. votes = generic.GenericRelation(Vote)
  35. _use_markdown = True
  36. _escape_html = False #markdow does the escaping
  37. _urlize = False
  38. class Meta:
  39. abstract = True
  40. app_label = 'askbot'
  41. def get_comments(self, visitor = None):
  42. """returns comments for a post, annotated with
  43. ``upvoted_by_user`` parameter, if visitor is logged in
  44. otherwise, returns query set for all comments to a given post
  45. """
  46. if visitor.is_anonymous():
  47. return self.comments.all().order_by('id')
  48. else:
  49. comment_content_type = ContentType.objects.get_for_model(Comment)
  50. #a fancy query to annotate comments with the visitor votes
  51. comments = self.comments.extra(
  52. select = SortedDict([
  53. (
  54. 'upvoted_by_user',
  55. 'SELECT COUNT(*) from vote, comment '
  56. 'WHERE vote.user_id = %s AND '
  57. 'vote.content_type_id = %s AND '
  58. 'vote.object_id = comment.id',
  59. )
  60. ]),
  61. select_params = (visitor.id, comment_content_type.id)
  62. ).order_by('id')
  63. return comments
  64. #todo: maybe remove this wnen post models are unified
  65. def get_text(self):
  66. return self.text
  67. def get_snippet(self):
  68. """returns an abbreviated snippet of the content
  69. """
  70. return html_utils.strip_tags(self.html)[:120] + ' ...'
  71. def add_comment(self, comment=None, user=None, added_at=None):
  72. if added_at is None:
  73. added_at = datetime.datetime.now()
  74. if None in (comment ,user):
  75. raise Exception('arguments comment and user are required')
  76. #Comment = models.get_model('askbot','Comment')#todo: forum hardcoded
  77. comment = Comment(
  78. content_object=self,
  79. comment=comment,
  80. user=user,
  81. added_at=added_at
  82. )
  83. comment.parse_and_save(author = user)
  84. self.comment_count = self.comment_count + 1
  85. self.save()
  86. #tried to add this to bump updated question
  87. #in most active list, but it did not work
  88. #becase delayed email updates would be triggered
  89. #for cases where user did not subscribe for them
  90. #
  91. #need to redo the delayed alert sender
  92. #
  93. #origin_post = self.get_origin_post()
  94. #if origin_post == self:
  95. # self.last_activity_at = added_at
  96. # self.last_activity_by = user
  97. #else:
  98. # origin_post.last_activity_at = added_at
  99. # origin_post.last_activity_by = user
  100. # origin_post.save()
  101. return comment
  102. def get_global_tag_based_subscribers(
  103. self,
  104. tag_mark_reason = None,
  105. subscription_records = None
  106. ):
  107. """returns a list of users who either follow or "do not ignore"
  108. the given set of tags, depending on the tag_mark_reason
  109. ``subscription_records`` - query set of ``~askbot.models.EmailFeedSetting``
  110. this argument is used to reduce number of database queries
  111. """
  112. if tag_mark_reason == 'good':
  113. email_tag_filter_strategy = const.INCLUDE_INTERESTING
  114. user_set_getter = User.objects.filter
  115. elif tag_mark_reason == 'bad':
  116. email_tag_filter_strategy = const.EXCLUDE_IGNORED
  117. user_set_getter = User.objects.exclude
  118. else:
  119. raise ValueError('Uknown value of tag mark reason %s' % tag_mark_reason)
  120. #part 1 - find users who follow or not ignore the set of tags
  121. tag_names = self.get_tag_names()
  122. tag_selections = MarkedTag.objects.filter(
  123. tag__name__in = tag_names,
  124. reason = tag_mark_reason
  125. )
  126. subscribers = set(
  127. user_set_getter(
  128. tag_selections__in = tag_selections
  129. ).filter(
  130. notification_subscriptions__in = subscription_records
  131. ).filter(
  132. email_tag_filter_strategy = email_tag_filter_strategy
  133. )
  134. )
  135. #part 2 - find users who follow or not ignore tags via wildcard selections
  136. #inside there is a potentially time consuming loop
  137. if askbot_settings.USE_WILDCARD_TAGS:
  138. #todo: fix this
  139. #this branch will not scale well
  140. #because we have to loop through the list of users
  141. #in python
  142. if tag_mark_reason == 'good':
  143. empty_wildcard_filter = {'interesting_tags__exact': ''}
  144. wildcard_tags_attribute = 'interesting_tags'
  145. update_subscribers = lambda the_set, item: the_set.add(item)
  146. elif tag_mark_reason == 'bad':
  147. empty_wildcard_filter = {'ignored_tags__exact': ''}
  148. wildcard_tags_attribute = 'ignored_tags'
  149. update_subscribers = lambda the_set, item: the_set.discard(item)
  150. potential_wildcard_subscribers = User.objects.filter(
  151. notification_subscriptions__in = subscription_records
  152. ).filter(
  153. email_tag_filter_strategy = email_tag_filter_strategy
  154. ).exclude(
  155. **empty_wildcard_filter #need this to limit size of the loop
  156. )
  157. for potential_subscriber in potential_wildcard_subscribers:
  158. wildcard_tags = getattr(
  159. potential_subscriber,
  160. wildcard_tags_attribute
  161. ).split(' ')
  162. if tags_match_some_wildcard(tag_names, wildcard_tags):
  163. update_subscribers(subscribers, potential_subscriber)
  164. return subscribers
  165. def get_global_instant_notification_subscribers(self):
  166. """returns a set of subscribers to post according to tag filters
  167. both - subscribers who ignore tags or who follow only
  168. specific tags
  169. this method in turn calls several more specialized
  170. subscriber retrieval functions
  171. todo: retrieval of wildcard tag followers ignorers
  172. won't scale at all
  173. """
  174. subscriber_set = set()
  175. global_subscriptions = EmailFeedSetting.objects.filter(
  176. feed_type = 'q_all',
  177. frequency = 'i'
  178. )
  179. #segment of users who have tag filter turned off
  180. global_subscribers = User.objects.filter(
  181. email_tag_filter_strategy = const.INCLUDE_ALL
  182. )
  183. subscriber_set.update(global_subscribers)
  184. #segment of users who want emails on selected questions only
  185. subscriber_set.update(
  186. self.get_global_tag_based_subscribers(
  187. subscription_records = global_subscriptions,
  188. tag_mark_reason = 'good'
  189. )
  190. )
  191. #segment of users who want to exclude ignored tags
  192. subscriber_set.update(
  193. self.get_global_tag_based_subscribers(
  194. subscription_records = global_subscriptions,
  195. tag_mark_reason = 'bad'
  196. )
  197. )
  198. return subscriber_set
  199. def get_instant_notification_subscribers(
  200. self,
  201. potential_subscribers = None,
  202. mentioned_users = None,
  203. exclude_list = None,
  204. ):
  205. """get list of users who have subscribed to
  206. receive instant notifications for a given post
  207. this method works for questions and answers
  208. Arguments:
  209. * ``potential_subscribers`` is not used here! todo: why? - clean this out
  210. parameter is left for the uniformity of the interface
  211. (Comment method does use it)
  212. normally these methods would determine the list
  213. :meth:`~askbot.models.question.Question.get_response_recipients`
  214. :meth:`~askbot.models.question.Answer.get_response_recipients`
  215. - depending on the type of the post
  216. * ``mentioned_users`` - users, mentioned in the post for the first time
  217. * ``exclude_list`` - users who must be excluded from the subscription
  218. Users who receive notifications are:
  219. * of ``mentioned_users`` - those who subscribe for the instant
  220. updates on the @name mentions
  221. * those who follow the parent question
  222. * global subscribers (any personalized tag filters are applied)
  223. * author of the question who subscribe to instant updates
  224. on questions that they asked
  225. * authors or any answers who subsribe to instant updates
  226. on the questions which they answered
  227. """
  228. #print '------------------'
  229. #print 'in content function'
  230. subscriber_set = set()
  231. #print 'potential subscribers: ', potential_subscribers
  232. #1) mention subscribers - common to questions and answers
  233. if mentioned_users:
  234. mention_subscribers = EmailFeedSetting.objects.filter_subscribers(
  235. potential_subscribers = mentioned_users,
  236. feed_type = 'm_and_c',
  237. frequency = 'i'
  238. )
  239. subscriber_set.update(mention_subscribers)
  240. origin_post = self.get_origin_post()
  241. #print origin_post
  242. #2) individually selected - make sure that users
  243. #are individual subscribers to this question
  244. selective_subscribers = origin_post.followed_by.all()
  245. #print 'question followers are ', [s for s in selective_subscribers]
  246. if selective_subscribers:
  247. selective_subscribers = EmailFeedSetting.objects.filter_subscribers(
  248. potential_subscribers = selective_subscribers,
  249. feed_type = 'q_sel',
  250. frequency = 'i'
  251. )
  252. subscriber_set.update(selective_subscribers)
  253. #print 'selective subscribers: ', selective_subscribers
  254. #3) whole forum subscribers
  255. global_subscribers = origin_post.get_global_instant_notification_subscribers()
  256. subscriber_set.update(global_subscribers)
  257. #4) question asked by me (todo: not "edited_by_me" ???)
  258. question_author = origin_post.author
  259. if EmailFeedSetting.objects.filter(
  260. subscriber = question_author,
  261. frequency = 'i',
  262. feed_type = 'q_ask'
  263. ):
  264. subscriber_set.add(question_author)
  265. #4) questions answered by me -make sure is that people
  266. #are authors of the answers to this question
  267. #todo: replace this with a query set method
  268. answer_authors = set()
  269. for answer in origin_post.answers.all():
  270. authors = answer.get_author_list()
  271. answer_authors.update(authors)
  272. if answer_authors:
  273. answer_subscribers = EmailFeedSetting.objects.filter_subscribers(
  274. potential_subscribers = answer_authors,
  275. frequency = 'i',
  276. feed_type = 'q_ans',
  277. )
  278. subscriber_set.update(answer_subscribers)
  279. #print 'answer subscribers: ', answer_subscribers
  280. #print 'exclude_list is ', exclude_list
  281. subscriber_set -= set(exclude_list)
  282. #print 'final subscriber set is ', subscriber_set
  283. return list(subscriber_set)
  284. def get_latest_revision(self):
  285. return self.revisions.all().order_by('-revised_at')[0]
  286. def get_latest_revision_number(self):
  287. return self.get_latest_revision().revision
  288. def get_time_of_last_edit(self):
  289. if self.last_edited_at:
  290. return self.last_edited_at
  291. else:
  292. return self.added_at
  293. def get_owner(self):
  294. return self.author
  295. def get_author_list(
  296. self,
  297. include_comments = False,
  298. recursive = False,
  299. exclude_list = None):
  300. #todo: there may be a better way to do these queries
  301. authors = set()
  302. authors.update([r.author for r in self.revisions.all()])
  303. if include_comments:
  304. authors.update([c.user for c in self.comments.all()])
  305. if recursive:
  306. if hasattr(self, 'answers'):
  307. for a in self.answers.exclude(deleted = True):
  308. authors.update(a.get_author_list( include_comments = include_comments ) )
  309. if exclude_list:
  310. authors -= set(exclude_list)
  311. return list(authors)
  312. def passes_tag_filter_for_user(self, user):
  313. question = self.get_origin_post()
  314. if user.email_tag_filter_strategy == const.INCLUDE_INTERESTING:
  315. #at least some of the tags must be marked interesting
  316. return user.has_affinity_to_question(
  317. question,
  318. affinity_type = 'like'
  319. )
  320. elif user.email_tag_filter_strategy == const.EXCLUDE_IGNORED:
  321. return not user.has_affinity_to_question(
  322. question,
  323. affinity_type = 'dislike'
  324. )
  325. elif user.email_tag_filter_strategy == const.INCLUDE_ALL:
  326. return True
  327. else:
  328. raise ValueError(
  329. 'unexpected User.email_tag_filter_strategy %s' \
  330. % user.email_tag_filter_strategy
  331. )
  332. def post_get_last_update_info(self):#todo: rename this subroutine
  333. when = self.added_at
  334. who = self.author
  335. if self.last_edited_at and self.last_edited_at > when:
  336. when = self.last_edited_at
  337. who = self.last_edited_by
  338. comments = self.comments.all()
  339. if len(comments) > 0:
  340. for c in comments:
  341. if c.added_at > when:
  342. when = c.added_at
  343. who = c.user
  344. return when, who