From 13574985075aa595384f6d24e871f27510221a5f Mon Sep 17 00:00:00 2001 From: Tomasz Zielinski Date: Fri, 2 Dec 2011 01:10:22 +0100 Subject: Tickets 104, 107: Major transplant of most Question methods --- askbot/management/commands/fix_question_tags.py | 2 +- askbot/models/__init__.py | 2 +- askbot/models/content.py | 32 +++- askbot/models/question.py | 238 +++++++++++------------- askbot/tests/db_api_tests.py | 4 +- askbot/views/readers.py | 6 +- askbot/views/writers.py | 2 +- 7 files changed, 147 insertions(+), 139 deletions(-) diff --git a/askbot/management/commands/fix_question_tags.py b/askbot/management/commands/fix_question_tags.py index 47fc4102..aec3de3b 100644 --- a/askbot/management/commands/fix_question_tags.py +++ b/askbot/management/commands/fix_question_tags.py @@ -59,7 +59,7 @@ class Command(NoArgsCommand): tagnames = forms.TagNamesField().clean(question.tagnames) - question.update_tags( + question.thread.update_tags( tagnames = tagnames, user = user, timestamp = timestamp diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index d711dac6..c9c49ae9 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -1011,7 +1011,7 @@ def user_retag_question( silent = False ): self.assert_can_retag_question(question) - question.retag( + question.thread.retag( retagged_by = self, retagged_at = timestamp, tagnames = tags, diff --git a/askbot/models/content.py b/askbot/models/content.py index adb9d428..5ce9682e 100644 --- a/askbot/models/content.py +++ b/askbot/models/content.py @@ -492,6 +492,34 @@ class Content(models.Model): comment.save() return new_question + def _repost_as_answer(self, question = None): + """posts question as answer to another question, + but does not delete the question, + but moves all the comments to the new answer""" + if not self.is_question(): + raise NotImplementedError + revisions = self.revisions.all().order_by('revised_at') + rev0 = revisions[0] + new_answer = rev0.author.post_answer( + question = question, + body_text = rev0.text, + wiki = self.wiki, + timestamp = rev0.revised_at + ) + if len(revisions) > 1: + for rev in revisions: + rev.author.edit_answer( + answer = new_answer, + body_text = rev.text, + revision_comment = rev.summary, + timestamp = rev.revised_at + ) + for comment in self.comments.all(): + comment.content_object = new_answer + comment.save() + return new_answer + + def swap_with_question(self, new_title = None): """swaps answer with the question it belongs to and sets the title of question to ``new_title`` @@ -504,7 +532,7 @@ class Content(models.Model): new_question = self._repost_as_question(new_title = new_title) #2) post question (all revisions and comments) as answer - new_answer = self.question.repost_as_answer(question = new_question) + new_answer = self.question._repost_as_answer(question = new_question) #3) assign all remaining answers to the new question self.question.answers.update(question = new_question) @@ -678,7 +706,7 @@ class Content(models.Model): # Update the Question tag associations if latest_revision.tagnames != tags: - self.update_tags(tagnames = tags, user = edited_by, timestamp = edited_at) + self.thread.update_tags(tagnames = tags, user = edited_by, timestamp = edited_at) # Create a new revision self.add_revision( diff --git a/askbot/models/question.py b/askbot/models/question.py index 52600313..84816b18 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -112,7 +112,7 @@ class QuestionQuerySet(models.query.QuerySet): question.wikified_at = added_at question.parse_and_save(author = author) - question.update_tags(tagnames = tagnames, user = author, timestamp = added_at) + question.thread.update_tags(tagnames = tagnames, user = author, timestamp = added_at) question.add_revision( author = author, @@ -482,9 +482,34 @@ class Thread(models.Model): self.save() def update_answer_count(self): - self.answer_count = self._question().get_answers().count() + self.answer_count = self.get_answers().count() self.save() + def get_answers(self, user=None): + """returns query set for answers to this question + that may be shown to the given user + """ + thread_question = self._question() + + if user is None or user.is_anonymous(): + return thread_question.answers.filter(deleted=False) + else: + if user.is_administrator() or user.is_moderator(): + return thread_question.answers.all() + else: + return thread_question.answers.filter( + models.Q(deleted = False) | models.Q(author = user) \ + | models.Q(deleted_by = user) + ) + + + def get_similarity(self, other_question = None): + """return number of tags in the other question + that overlap with the current question (self) + """ + my_tags = set(self._question().get_tag_names()) + others_tags = set(other_question.get_tag_names()) + return len(my_tags & others_tags) def get_similar_questions(self): """ @@ -507,59 +532,13 @@ class Thread(models.Model): similar_questions = list(similar_questions) for question in similar_questions: - question.similarity = thread_question.get_similarity(other_question=question) + question.similarity = self.get_similarity(other_question=question) similar_questions.sort(key=operator.attrgetter('similarity'), reverse=True) return similar_questions[:10] return LazyList(get_data) - - -class Question(content.Content): - # TODO: Eventually move this into Content/Post model - thread = models.ForeignKey('Thread', unique=True, related_name='questions') - - #todo: this really becomes thread, - #except property post_type goes to Post - post_type = 'question' - title = models.CharField(max_length=300) - tags = models.ManyToManyField('Tag', related_name='questions') - #todo: answer accepted will be replaced with - #accepted_answer foreign key (nullable) - answer_accepted = models.BooleanField(default=False) - closed = models.BooleanField(default=False) - closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions') - closed_at = models.DateTimeField(null=True, blank=True) - close_reason = models.SmallIntegerField( - choices=const.CLOSE_REASONS, - null=True, - blank=True - ) - followed_by = models.ManyToManyField(User, related_name='followed_questions') - - # Denormalised data - view_count = models.PositiveIntegerField(default=0) - last_activity_at = models.DateTimeField(default=datetime.datetime.now) - last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions') - tagnames = models.CharField(max_length=125) - summary = models.CharField(max_length=180) - - favorited_by = models.ManyToManyField(User, through='FavoriteQuestion', related_name='favorite_questions') - #note: anonymity here applies to question only, but - #the field will still go to thread - #maybe we should rename it to is_question_anonymous - #we might have to duplicate the is_anonymous on the Post, - #if we are to allow anonymous answers - #the reason is that the title and tags belong to thread, - #but the question body to Post - is_anonymous = models.BooleanField(default=False) - - objects = QuestionManager() - - class Meta(content.Content.Meta): - db_table = u'question' - def remove_author_anonymity(self): """removes anonymous flag from the question and all its revisions @@ -569,23 +548,16 @@ class Question(content.Content): #note: see note for the is_anonymous field #it is important that update method is called - not save, #because we do not want the signals to fire here - Question.objects.filter(id = self.id).update(is_anonymous = False) - self.revisions.all().update(is_anonymous = False) - - def get_similarity(self, other_question = None): - """return number of tags in the other question - that overlap with the current question (self) - """ - my_tags = set(self.get_tag_names()) - others_tags = set(other_question.get_tag_names()) - return len(my_tags & others_tags) + thread_question = self._question() + Question.objects.filter(id=thread_question.id).update(is_anonymous=False) + thread_question.revisions.all().update(is_anonymous=False) def update_tags(self, tagnames = None, user = None, timestamp = None): """ Updates Tag associations for a question to match the given tagname string. - When tags are removed and their use count hits 0 - the tag is + When tags are removed and their use count hits 0 - the tag is automatically deleted. When an added tag does not exist - it is created @@ -594,8 +566,9 @@ class Question(content.Content): A signal tags updated is sent """ + thread_question = self._question() - previous_tags = list(self.tags.all()) + previous_tags = list(thread_question.tags.all()) previous_tagnames = set([tag.name for tag in previous_tags]) updated_tagnames = set(t for t in tagnames.split(' ')) @@ -607,7 +580,7 @@ class Question(content.Content): #remove tags from the question's tags many2many relation if removed_tagnames: removed_tags = [tag for tag in previous_tags if tag.name in removed_tagnames] - self.tags.remove(*removed_tags) + thread_question.tags.remove(*removed_tags) #if any of the removed tags reached use count == 1 that means they must be deleted for tag in removed_tags: @@ -647,14 +620,14 @@ class Question(content.Content): added_tags = reused_tags #finally add tags to the relation and extend the modified list - self.tags.add(*added_tags) + thread_question.tags.add(*added_tags) modified_tags.extend(added_tags) #if there are any modified tags, update their use counts if modified_tags: Tag.objects.update_use_counts(modified_tags) signals.tags_updated.send(None, - question = self, + question = thread_question, tags = modified_tags, user = user, timestamp = timestamp @@ -663,67 +636,28 @@ class Question(content.Content): return False - def repost_as_answer(self, question = None): - """posts question as answer to another question, - but does not delete the question, - but moves all the comments to the new answer""" - #todo: goes to Thread. - revisions = self.revisions.all().order_by('revised_at') - rev0 = revisions[0] - new_answer = rev0.author.post_answer( - question = question, - body_text = rev0.text, - wiki = self.wiki, - timestamp = rev0.revised_at - ) - if len(revisions) > 1: - for rev in revisions: - rev.author.edit_answer( - answer = new_answer, - body_text = rev.text, - revision_comment = rev.summary, - timestamp = rev.revised_at - ) - for comment in self.comments.all(): - comment.content_object = new_answer - comment.save() - return new_answer - - def get_answers(self, user = None): - """returns query set for answers to this question - that may be shown to the given user - """ - - if user is None or user.is_anonymous(): - return self.answers.filter(deleted=False) - else: - if user.is_administrator() or user.is_moderator(): - return self.answers.all() - else: - return self.answers.filter( - models.Q(deleted = False) | models.Q(author = user) \ - | models.Q(deleted_by = user) - ) - def retag(self, retagged_by=None, retagged_at=None, tagnames=None, silent=False): if None in (retagged_by, retagged_at, tagnames): raise Exception('arguments retagged_at, retagged_by and tagnames are required') + + thread_question = self._question() + # Update the Question itself - self.tagnames = tagnames + thread_question.tagnames = tagnames if silent == False: - self.last_edited_at = retagged_at - #self.last_activity_at = retagged_at - self.last_edited_by = retagged_by - #self.last_activity_by = retagged_by - self.save() + thread_question.last_edited_at = retagged_at + #thread_question.last_activity_at = retagged_at + thread_question.last_edited_by = retagged_by + #thread_question.last_activity_by = retagged_by + thread_question.save() # Update the Question's tag associations - self.update_tags(tagnames = tagnames, user = retagged_by, timestamp = retagged_at) + self.update_tags(tagnames=tagnames, user=retagged_by, timestamp=retagged_at) # Create a new revision - latest_revision = self.get_latest_revision() + latest_revision = thread_question.get_latest_revision() PostRevision.objects.create_question_revision( - question = self, + question = thread_question, title = latest_revision.title, author = retagged_by, revised_at = retagged_at, @@ -732,30 +666,76 @@ class Question(content.Content): text = latest_revision.text ) - def _get_slug(self): - return slugify(self.title) - - slug = property(_get_slug) - def has_favorite_by_user(self, user): if not user.is_authenticated(): return False - return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0 + return FavoriteQuestion.objects.filter(question=self._question(), user=user).exists() def get_last_update_info(self): - when, who = self.post_get_last_update_info() + thread_question = self._question() - answers = self.answers.all() - if len(answers) > 0: - for a in answers: - a_when, a_who = a.post_get_last_update_info() - if a_when > when: - when = a_when - who = a_who + when, who = thread_question.post_get_last_update_info() + + answers = thread_question.answers.all() + for a in answers: + a_when, a_who = a.post_get_last_update_info() + if a_when > when: + when = a_when + who = a_who return when, who + +class Question(content.Content): + # TODO: Eventually move this into Content/Post model + thread = models.ForeignKey('Thread', unique=True, related_name='questions') + + #todo: this really becomes thread, + #except property post_type goes to Post + post_type = 'question' + title = models.CharField(max_length=300) + tags = models.ManyToManyField('Tag', related_name='questions') + #todo: answer accepted will be replaced with + #accepted_answer foreign key (nullable) + answer_accepted = models.BooleanField(default=False) + closed = models.BooleanField(default=False) + closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions') + closed_at = models.DateTimeField(null=True, blank=True) + close_reason = models.SmallIntegerField( + choices=const.CLOSE_REASONS, + null=True, + blank=True + ) + followed_by = models.ManyToManyField(User, related_name='followed_questions') + + # Denormalised data + view_count = models.PositiveIntegerField(default=0) + last_activity_at = models.DateTimeField(default=datetime.datetime.now) + last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions') + tagnames = models.CharField(max_length=125) + summary = models.CharField(max_length=180) + + favorited_by = models.ManyToManyField(User, through='FavoriteQuestion', related_name='favorite_questions') + #note: anonymity here applies to question only, but + #the field will still go to thread + #maybe we should rename it to is_question_anonymous + #we might have to duplicate the is_anonymous on the Post, + #if we are to allow anonymous answers + #the reason is that the title and tags belong to thread, + #but the question body to Post + is_anonymous = models.BooleanField(default=False) + + objects = QuestionManager() + + class Meta(content.Content.Meta): + db_table = u'question' + + def _get_slug(self): + return slugify(self.title) + + slug = property(_get_slug) + if getattr(settings, 'USE_SPHINX_SEARCH', False): from djangosphinx.models import SphinxSearch Question.add_to_class( diff --git a/askbot/tests/db_api_tests.py b/askbot/tests/db_api_tests.py index 0d7fd666..ea13bce1 100644 --- a/askbot/tests/db_api_tests.py +++ b/askbot/tests/db_api_tests.py @@ -85,7 +85,7 @@ class DBApiTests(AskbotTestCase): tags = 'aoeuaoeu', revision_comment = 'hahahah' ) - q.remove_author_anonymity() + q.thread.remove_author_anonymity() q = self.reload_object(q) self.assertFalse(q.is_anonymous) for rev in q.revisions.all(): @@ -127,7 +127,7 @@ class DBApiTests(AskbotTestCase): self.post_answer(user = self.other_user) self.user.delete_question(self.question) self.assert_post_is_deleted(self.question) - answer_count = self.question.get_answers(user = self.user).count() + answer_count = self.question.thread.get_answers(user = self.user).count() answer = self.question.answers.all()[0] self.assert_post_is_not_deleted(answer) self.assertTrue(answer_count == 1) diff --git a/askbot/views/readers.py b/askbot/views/readers.py index 5b4a2bc8..4580a406 100644 --- a/askbot/views/readers.py +++ b/askbot/views/readers.py @@ -460,7 +460,7 @@ def question(request, id):#refactor - long subroutine. display question body, an logging.debug('answer_sort_method=' + unicode(answer_sort_method)) #load answers - answers = question.get_answers(user = request.user) + answers = question.thread.get_answers(user = request.user) answers = answers.select_related(depth=1) user_answer_votes = {} @@ -506,7 +506,7 @@ def question(request, id):#refactor - long subroutine. display question body, an request.session['question_view_times'] = {} last_seen = request.session['question_view_times'].get(question.id, None) - updated_when, updated_who = question.get_last_update_info() + updated_when, updated_who = question.thread.get_last_update_info() if updated_who != request.user: if last_seen: @@ -548,7 +548,7 @@ def question(request, id):#refactor - long subroutine. display question body, an } paginator_context = extra_tags.cnprog_paginator(paginator_data) - favorited = question.has_favorite_by_user(request.user) + favorited = question.thread.has_favorite_by_user(request.user) user_question_vote = 0 if request.user.is_authenticated(): votes = question.votes.select_related().filter(user=request.user) diff --git a/askbot/views/writers.py b/askbot/views/writers.py index 5f073b37..1f51cd3c 100644 --- a/askbot/views/writers.py +++ b/askbot/views/writers.py @@ -392,7 +392,7 @@ def edit_question(request, id): if form.has_changed(): if form.cleaned_data['reveal_identity']: - question.remove_author_anonymity() + question.thread.remove_author_anonymity() is_anon_edit = form.cleaned_data['stay_anonymous'] is_wiki = form.cleaned_data.get('wiki', question.wiki) -- cgit v1.2.3-1-g7c22