]> git.openstreetmap.org Git - osqa.git/blobdiff - forum/models/question.py
Improvements on full text search.
[osqa.git] / forum / models / question.py
old mode 100755 (executable)
new mode 100644 (file)
index f916e65..9e8dcf6
-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
+