-from base import *\r
-from tag import Tag\r
-\r
-class QuestionManager(models.Manager):\r
- @staticmethod\r
- def create_new(cls, title=None,author=None,added_at=None, wiki=False,tagnames=None,summary=None, text=None):\r
- question = Question(\r
- title = title,\r
- author = author,\r
- added_at = added_at,\r
- last_activity_at = added_at,\r
- last_activity_by = author,\r
- wiki = wiki,\r
- tagnames = tagnames,\r
- html = text,\r
- summary = summary\r
- )\r
- if question.wiki:\r
- question.last_edited_by = question.author\r
- question.last_edited_at = added_at\r
- question.wikified_at = added_at\r
-\r
- question.save()\r
-\r
- # create the first revision\r
- QuestionRevision.objects.create(\r
- question = question,\r
- revision = 1,\r
- title = question.title,\r
- author = author,\r
- revised_at = added_at,\r
- tagnames = question.tagnames,\r
- summary = CONST['default_version'],\r
- text = text\r
- )\r
- return question\r
-\r
- def update_tags(self, question, tagnames, user):\r
- """\r
- Updates Tag associations for a question to match the given\r
- tagname string.\r
-\r
- Returns ``True`` if tag usage counts were updated as a result,\r
- ``False`` otherwise.\r
- """\r
-\r
- current_tags = list(question.tags.all())\r
- current_tagnames = set(t.name for t in current_tags)\r
- updated_tagnames = set(t for t in tagnames.split(' ') if t)\r
- modified_tags = []\r
-\r
- removed_tags = [t for t in current_tags\r
- if t.name not in updated_tagnames]\r
- if removed_tags:\r
- modified_tags.extend(removed_tags)\r
- question.tags.remove(*removed_tags)\r
-\r
- added_tagnames = updated_tagnames - current_tagnames\r
- if added_tagnames:\r
- added_tags = Tag.objects.get_or_create_multiple(added_tagnames,\r
- user)\r
- modified_tags.extend(added_tags)\r
- question.tags.add(*added_tags)\r
-\r
- if modified_tags:\r
- Tag.objects.update_use_counts(modified_tags)\r
- return True\r
-\r
- return False\r
-\r
- def update_answer_count(self, question):\r
- """\r
- Executes an UPDATE query to update denormalised data with the\r
- number of answers the given question has.\r
- """\r
-\r
- # for some reasons, this Answer class failed to be imported,\r
- # although we have imported all classes from models on top.\r
- from answer import Answer\r
- self.filter(id=question.id).update(\r
- answer_count=Answer.objects.get_answers_from_question(question).filter(deleted=False).count())\r
-\r
- def update_view_count(self, question):\r
- """\r
- update counter+1 when user browse question page\r
- """\r
- self.filter(id=question.id).update(view_count = question.view_count + 1)\r
-\r
- def update_favorite_count(self, question):\r
- """\r
- update favourite_count for given question\r
- """\r
- self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count())\r
-\r
- def get_similar_questions(self, question):\r
- """\r
- Get 10 similar questions for given one.\r
- This will search the same tag list for give question(by exactly same string) first.\r
- Questions with the individual tags will be added to list if above questions are not full.\r
- """\r
- #print datetime.datetime.now()\r
- questions = list(self.filter(tagnames = question.tagnames, deleted=False).all())\r
-\r
- tags_list = question.tags.all()\r
- for tag in tags_list:\r
- extend_questions = self.filter(tags__id = tag.id, deleted=False)[:50]\r
- for item in extend_questions:\r
- if item not in questions and len(questions) < 10:\r
- questions.append(item)\r
-\r
- #print datetime.datetime.now()\r
- return questions\r
-\r
-class Question(Content, DeletableContent):\r
- title = models.CharField(max_length=300)\r
- tags = models.ManyToManyField('Tag', related_name='questions')\r
- answer_accepted = models.BooleanField(default=False)\r
- closed = models.BooleanField(default=False)\r
- closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions')\r
- closed_at = models.DateTimeField(null=True, blank=True)\r
- close_reason = models.SmallIntegerField(choices=CLOSE_REASONS, null=True, blank=True)\r
- followed_by = models.ManyToManyField(User, related_name='followed_questions')\r
-\r
- # Denormalised data\r
- answer_count = models.PositiveIntegerField(default=0)\r
- view_count = models.PositiveIntegerField(default=0)\r
- favourite_count = models.PositiveIntegerField(default=0)\r
- last_activity_at = models.DateTimeField(default=datetime.datetime.now)\r
- last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions')\r
- tagnames = models.CharField(max_length=125)\r
- summary = models.CharField(max_length=180)\r
-\r
- favorited_by = models.ManyToManyField(User, through='FavoriteQuestion', related_name='favorite_questions') \r
-\r
- objects = QuestionManager()\r
-\r
- class Meta(Content.Meta):\r
- db_table = u'question'\r
-\r
- def delete(self):\r
- super(Question, self).delete()\r
- try:\r
- ping_google()\r
- except Exception:\r
- logging.debug('problem pinging google did you register you sitemap with google?')\r
-\r
- def save(self, **kwargs):\r
- """\r
- Overridden to manually manage addition of tags when the object\r
- is first saved.\r
-\r
- This is required as we're using ``tagnames`` as the sole means of\r
- adding and editing tags.\r
- """\r
- initial_addition = (self.id is None)\r
- \r
- super(Question, self).save(**kwargs)\r
-\r
- if initial_addition:\r
- tags = Tag.objects.get_or_create_multiple(self.tagname_list(),\r
- self.author)\r
- self.tags.add(*tags)\r
- Tag.objects.update_use_counts(tags)\r
-\r
- def tagname_list(self):\r
- """Creates a list of Tag names from the ``tagnames`` attribute."""\r
- return [name for name in self.tagnames.split(u' ')]\r
-\r
- def tagname_meta_generator(self):\r
- return u','.join([unicode(tag) for tag in self.tagname_list()])\r
-\r
- def get_absolute_url(self):\r
- return '%s%s' % (reverse('question', args=[self.id]), django_urlquote(slugify(self.title)))\r
-\r
- def has_favorite_by_user(self, user):\r
- if not user.is_authenticated():\r
- return False\r
-\r
- return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0\r
-\r
- def get_answer_count_by_user(self, user_id):\r
- from answer import Answer\r
- query_set = Answer.objects.filter(author__id=user_id)\r
- return query_set.filter(question=self).count()\r
-\r
- def get_question_title(self):\r
- if self.closed:\r
- attr = CONST['closed']\r
- elif self.deleted:\r
- attr = CONST['deleted']\r
- else:\r
- attr = None\r
- if attr is not None:\r
- return u'%s %s' % (self.title, attr)\r
- else:\r
- return self.title\r
-\r
- def get_revision_url(self):\r
- return reverse('question_revisions', args=[self.id])\r
-\r
- def get_latest_revision(self):\r
- return self.revisions.all()[0]\r
-\r
- def get_last_update_info(self):\r
- when, who = self.post_get_last_update_info()\r
-\r
- answers = self.answers.all()\r
- if len(answers) > 0:\r
- for a in answers:\r
- a_when, a_who = a.post_get_last_update_info()\r
- if a_when > when:\r
- when = a_when\r
- who = a_who\r
-\r
- return when, who\r
-\r
- def get_update_summary(self,last_reported_at=None,recipient_email=''):\r
- edited = False\r
- if self.last_edited_at and self.last_edited_at > last_reported_at:\r
- if self.last_edited_by.email != recipient_email:\r
- edited = True\r
- comments = []\r
- for comment in self.comments.all():\r
- if comment.added_at > last_reported_at and comment.user.email != recipient_email:\r
- comments.append(comment)\r
- new_answers = []\r
- answer_comments = []\r
- modified_answers = []\r
- commented_answers = []\r
- import sets\r
- commented_answers = sets.Set([])\r
- for answer in self.answers.all():\r
- if (answer.added_at > last_reported_at and answer.author.email != recipient_email):\r
- new_answers.append(answer)\r
- if (answer.last_edited_at\r
- and answer.last_edited_at > last_reported_at\r
- and answer.last_edited_by.email != recipient_email):\r
- modified_answers.append(answer)\r
- for comment in answer.comments.all():\r
- if comment.added_at > last_reported_at and comment.user.email != recipient_email:\r
- commented_answers.add(answer)\r
- answer_comments.append(comment)\r
-\r
- #create the report\r
- if edited or new_answers or modified_answers or answer_comments:\r
- out = []\r
- if edited:\r
- out.append(_('%(author)s modified the question') % {'author':self.last_edited_by.username})\r
- if new_answers:\r
- names = sets.Set(map(lambda x: x.author.username,new_answers))\r
- people = ', '.join(names)\r
- out.append(_('%(people)s posted %(new_answer_count)s new answers') \\r
- % {'new_answer_count':len(new_answers),'people':people})\r
- if comments:\r
- names = sets.Set(map(lambda x: x.user.username,comments))\r
- people = ', '.join(names)\r
- out.append(_('%(people)s commented the question') % {'people':people})\r
- if answer_comments:\r
- names = sets.Set(map(lambda x: x.user.username,answer_comments))\r
- people = ', '.join(names)\r
- if len(commented_answers) > 1:\r
- out.append(_('%(people)s commented answers') % {'people':people})\r
- else:\r
- out.append(_('%(people)s commented an answer') % {'people':people})\r
- url = settings.APP_URL + self.get_absolute_url()\r
- retval = '<a href="%s">%s</a>:<br>\n' % (url,self.title)\r
- out = map(lambda x: '<li>' + x + '</li>',out)\r
- retval += '<ul>' + '\n'.join(out) + '</ul><br>\n'\r
- return retval\r
- else:\r
- return None\r
-\r
- def __unicode__(self):\r
- return self.title\r
-\r
- \r
-class QuestionView(models.Model):\r
- question = models.ForeignKey(Question, related_name='viewed')\r
- who = models.ForeignKey(User, related_name='question_views')\r
- when = models.DateTimeField()\r
-\r
- class Meta:\r
- app_label = 'forum'\r
-\r
-class FavoriteQuestion(models.Model):\r
- """A favorite Question of a User."""\r
- question = models.ForeignKey(Question)\r
- user = models.ForeignKey(User, related_name='user_favorite_questions')\r
- added_at = models.DateTimeField(default=datetime.datetime.now)\r
-\r
- class Meta:\r
- app_label = 'forum'\r
- db_table = u'favorite_question'\r
- def __unicode__(self):\r
- return '[%s] favorited at %s' %(self.user, self.added_at)\r
-\r
-class QuestionRevision(ContentRevision):\r
- """A revision of a Question."""\r
- question = models.ForeignKey(Question, related_name='revisions')\r
- title = models.CharField(max_length=300)\r
- tagnames = models.CharField(max_length=125)\r
-\r
- class Meta(ContentRevision.Meta):\r
- db_table = u'question_revision'\r
- ordering = ('-revision',)\r
-\r
- def get_question_title(self):\r
- return self.question.title\r
-\r
- def get_absolute_url(self):\r
- #print 'in QuestionRevision.get_absolute_url()'\r
- return reverse('question_revisions', args=[self.question.id])\r
-\r
- def save(self, **kwargs):\r
- """Looks up the next available revision number."""\r
- if not self.revision:\r
- self.revision = QuestionRevision.objects.filter(\r
- question=self.question).values_list('revision',\r
- flat=True)[0] + 1\r
- super(QuestionRevision, self).save(**kwargs)\r
-\r
- def __unicode__(self):\r
- return u'revision %s of %s' % (self.revision, self.title)\r
-\r
-class AnonymousQuestion(AnonymousContent):\r
- title = models.CharField(max_length=300)\r
- tagnames = models.CharField(max_length=125)\r
-\r
- def publish(self,user):\r
- added_at = datetime.datetime.now()\r
- QuestionManager.create_new(title=self.title, author=user, added_at=added_at,\r
- wiki=self.wiki, tagnames=self.tagnames,\r
- summary=self.summary, text=self.text)\r
- self.delete()\r
-\r
-from answer import Answer, AnswerManager\r
+from base import *
+from tag import Tag
+from django.utils.translation import ugettext as _
+from forum.modules.decorators import decoratable
+
+question_view = django.dispatch.Signal(providing_args=['instance', 'user'])
+
+class QuestionManager(NodeManager):
+ @decoratable.method
+ def search(self, keywords):
+ return self.filter(models.Q(title__icontains=keywords) | models.Q(body__icontains=keywords))
+
+class Question(Node):
+ class Meta(Node.Meta):
+ proxy = True
+
+ answer_count = DenormalizedField("children", node_type="answer", deleted=None)
+ favorite_count = DenormalizedField("actions", action_type="favorite", canceled=False)
+
+ friendly_name = _("question")
+ objects = QuestionManager()
+
+ @property
+ def closed(self):
+ return self.extra_action
+
+ @property
+ def view_count(self):
+ return self.extra_count
+
+ @property
+ def headline(self):
+ if self.marked:
+ return _('[closed] ') + self.title
+
+ if self.deleted:
+ return _('[deleted] ') + self.title
+
+ return self.title
+
+ @property
+ def answer_accepted(self):
+ return self.extra_ref is not None
+
+ @property
+ def accepted_answer(self):
+ return self.extra_ref
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ('question', (), {'id': self.id, 'slug': django_urlquote(slugify(self.title))})
+
+ def get_revision_url(self):
+ return reverse('question_revisions', args=[self.id])
+
+ def get_related_questions(self, count=10):
+ cache_key = '%s.related_questions:%d:%d' % (settings.APP_URL, count, self.id)
+ related_list = cache.get(cache_key)
+
+ if related_list is None:
+ related_list = Question.objects.values('id').filter(tags__id__in=[t.id for t in self.tags.all()]
+ ).exclude(id=self.id).filter(deleted=None).annotate(frequency=models.Count('id')).order_by('-frequency')[:count]
+ cache.set(cache_key, related_list, 60 * 60)
+
+ return [Question.objects.get(id=r['id']) for r in related_list]
+
+
+def question_viewed(instance, **kwargs):
+ instance.extra_count += 1
+ instance.save()
+
+question_view.connect(question_viewed)
+
+
+class QuestionSubscription(models.Model):
+ user = models.ForeignKey(User)
+ question = models.ForeignKey(Node)
+ auto_subscription = models.BooleanField(default=True)
+ last_view = models.DateTimeField(default=datetime.datetime.now())
+
+ class Meta:
+ app_label = 'forum'
+
+
+class QuestionRevision(NodeRevision):
+ class Meta:
+ proxy = True
+