From 4cd8037ca1add201f9f0a11654588094e39b06c8 Mon Sep 17 00:00:00 2001 From: Tomasz Zielinski Date: Mon, 12 Dec 2011 13:17:31 +0100 Subject: Misc tweaks --- .../0088_install__post_view__for__development.py | 3 + askbot/models/content.py | 4 +- askbot/models/post.py | 3 +- askbot/models/post_view.sql | 208 ++++++++++++++++++--- askbot/models/question.py | 75 ++++++-- .../default/templates/user_profile/user_stats.html | 4 +- askbot/tests/permission_assertion_tests.py | 1 - askbot/views/users.py | 6 +- 8 files changed, 246 insertions(+), 58 deletions(-) diff --git a/askbot/migrations/0088_install__post_view__for__development.py b/askbot/migrations/0088_install__post_view__for__development.py index 7d7d1ba9..1066dc5d 100644 --- a/askbot/migrations/0088_install__post_view__for__development.py +++ b/askbot/migrations/0088_install__post_view__for__development.py @@ -12,6 +12,9 @@ class Migration(SchemaMigration): create_post_view_sql = open( askbot.get_path_to('models/post_view.sql') ).read() + import warnings + warnings.filterwarnings("ignore", "Unknown table 'askbot.askbot_post'") # DROP VIEW might raise a warning so let's filter that out + db.execute('DROP VIEW IF EXISTS askbot_post') db.execute(create_post_view_sql) def backwards(self, orm): diff --git a/askbot/models/content.py b/askbot/models/content.py index 8424c8f6..d877de7e 100644 --- a/askbot/models/content.py +++ b/askbot/models/content.py @@ -860,9 +860,7 @@ class Content(models.Model): raise NotImplementedError def get_question_title(self): - if self.is_answer(): - return self.question.thread.title - elif self.is_question(): + if self.is_question(): if self.thread.closed: attr = const.POST_STATUS['closed'] elif self.deleted: diff --git a/askbot/models/post.py b/askbot/models/post.py index 11a3f774..62389e7a 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -23,8 +23,9 @@ class Post(content.Content): self_question = models.ForeignKey('Question', blank=True, null=True) question = property(fget=lambda self: self.self_answer.question) # to simulate Answer model + question_id = property(fget=lambda self: self.self_answer.question_id) # to simulate Answer model - thread = models.ForeignKey('Thread') + thread = models.ForeignKey('Thread', related_name='posts') objects = PostManager() diff --git a/askbot/models/post_view.sql b/askbot/models/post_view.sql index a2218d7d..2abac4a9 100644 --- a/askbot/models/post_view.sql +++ b/askbot/models/post_view.sql @@ -4,21 +4,32 @@ SQL for creating a database VIEW for the unmanaged `Post` model Tested with: SQLite3 +Important: String literals should be wrapped in single quotes (http://www.sqlite.org/lang_keywords.html) + */ -CREATE VIEW IF NOT EXISTS askbot_post AS +-- DROP VIEW IF EXISTS askbot_post; + +CREATE VIEW askbot_post AS + +/* + +Answers + +*/ SELECT - answer.id + 1000000 AS id, -- fake unique ID + answer.id + 1000000 AS id, -- fake unique ID - has to stay consistent with Post.parent_id for answer comments (defined below) ! /* Some required pseudo-fields */ - "answer" AS post_type, + 'answer' AS post_type, - joined_question.id AS parent_id, + NULL AS parent_id, joined_question.thread_id AS thread_id, answer.id AS self_answer_id, NULL AS self_question_id, + NULL AS self_comment_id, /* Shared fields from content.Content */ answer.author_id, @@ -59,49 +70,186 @@ ON joined_question.id=answer.question_id UNION +/* + +Questions + +*/ + SELECT - question.id AS id, -- fake unique ID + question.id AS id, -- fake unique ID - has to stay consistent with Post.parent_id for question comments (defined below) ! /* Some required pseudo-fields */ - "question" AS post_type, + 'question' AS post_type, NULL AS parent_id, - thread_id, + question.thread_id, + + NULL AS self_answer_id, + question.id AS self_question_id, + NULL AS self_comment_id, + + /* Shared fields from content.Content */ + question.author_id, + question.added_at, + + question.deleted, + question.deleted_at, + question.deleted_by_id, + + question.wiki, + question.wikified_at, + + question.locked, + question.locked_by_id, + question.locked_at, + + question.score, + question.vote_up_count, + question.vote_down_count, + + question.comment_count, + question.offensive_flag_count, + + question.last_edited_at, + question.last_edited_by_id, + + question.html, + question.text, + + question.summary, + + question.is_anonymous + +FROM question + + +UNION + +/* + +Comments to Questions + +*/ + + +SELECT + comment.id + 2000000 AS id, -- fake unique ID + + /* Some required pseudo-fields */ + 'comment' AS post_type, + + joined_question.id AS parent_id, -- has to stay consistent with Post.is for joined_questions !! + joined_question.thread_id AS thread_id, NULL AS self_answer_id, - id AS self_question_id, + NULL AS self_question_id, + comment.id AS self_comment_id, /* Shared fields from content.Content */ - author_id, - added_at, + comment.user_id AS author_id, + comment.added_at, + + 0 AS deleted, + NULL AS deleted_at, + NULL AS deleted_by_id, + + 0 AS wiki, + NULL AS wikified_at, + + 0 AS locked, + NULL AS locked_by_id, + NULL AS locked_at, + + comment.score, + comment.score AS vote_up_count, + 0 AS vote_down_count, + + 0 AS comment_count, + comment.offensive_flag_count, + + NULL AS last_edited_at, + NULL AS last_edited_by_id, + + comment.html, + NULL AS text, + + '' AS summary, + + 0 AS is_anonymous + +FROM comment + +INNER JOIN django_content_type AS ct +ON ct.id=comment.content_type_id AND ct.app_label='askbot' AND ct.model='question' + +INNER JOIN question AS joined_question +ON joined_question.id=comment.object_id + + +UNION + +/* + +Comments to Answers + +*/ + + +SELECT + comment.id + 2000000 AS id, -- fake unique ID + + /* Some required pseudo-fields */ + 'comment' AS post_type, + + joined_answer.id + 1000000 AS parent_id, -- has to stay consistent with Post.is for joined_questions !! + joined_question.thread_id AS thread_id, + + NULL AS self_answer_id, + NULL AS self_question_id, + comment.id AS self_comment_id, + + /* Shared fields from content.Content */ + comment.user_id AS author_id, + comment.added_at, + + 0 AS deleted, + NULL AS deleted_at, + NULL AS deleted_by_id, + + 0 AS wiki, + NULL AS wikified_at, + + 0 AS locked, + NULL AS locked_by_id, + NULL AS locked_at, - deleted, - deleted_at, - deleted_by_id, + comment.score, + comment.score AS vote_up_count, + 0 AS vote_down_count, - wiki, - wikified_at, + 0 AS comment_count, + comment.offensive_flag_count, - locked, - locked_by_id, - locked_at, + NULL AS last_edited_at, + NULL AS last_edited_by_id, - score, - vote_up_count, - vote_down_count, + comment.html, + NULL AS text, - comment_count, - offensive_flag_count, + '' AS summary, - last_edited_at, - last_edited_by_id, + 0 AS is_anonymous - html, - text, +FROM comment - summary, +INNER JOIN django_content_type AS ct +ON ct.id=comment.content_type_id AND ct.app_label='askbot' AND ct.model='answer' - is_anonymous +INNER JOIN answer AS joined_answer +ON joined_answer.id=comment.object_id +INNER JOIN question AS joined_question +ON joined_question.id=joined_answer.question_id -FROM question; \ No newline at end of file +; -- End of SQL statement diff --git a/askbot/models/question.py b/askbot/models/question.py index 018671bc..e4516a91 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -12,7 +12,7 @@ import askbot import askbot.conf from askbot.models.tag import Tag from askbot.models.base import AnonymousContent -from askbot.models.post import PostRevision +from askbot.models.post import Post, PostRevision from askbot.models.base import BaseQuerySetManager from askbot.models import content from askbot.models import signals @@ -147,6 +147,9 @@ class Thread(models.Model): def _question(self): return Question.objects.get(thread=self) + def get_absolute_url(self): + return self._question().get_absolute_url() + def update_favorite_count(self): self.favourite_count = FavoriteQuestion.objects.filter(thread=self).count() self.save() @@ -183,19 +186,32 @@ class Thread(models.Model): "Creates a list of Tag names from the ``tagnames`` attribute." return self.tagnames.split(u' ') + def get_title(self): + if self.closed: + attr = const.POST_STATUS['closed'] + elif self._question().deleted: + attr = const.POST_STATUS['deleted'] + else: + attr = None + if attr is not None: + return u'%s %s' % (self.title, attr) + else: + return self.title + + def tagname_meta_generator(self): + return u','.join([unicode(tag) for tag in self.get_tag_names()]) + 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) + return self.posts.get_answers().filter(deleted=False) else: if user.is_administrator() or user.is_moderator(): - return thread_question.answers.all() + return self.posts.get_answers() else: - return thread_question.answers.filter( + return self.posts.get_answers().filter( models.Q(deleted = False) | models.Q(author = user) \ | models.Q(deleted_by = user) ) @@ -373,18 +389,41 @@ class Thread(models.Model): return FavoriteQuestion.objects.filter(thread=self, user=user).exists() def get_last_update_info(self): - thread_question = self._question() - - 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 +# thread_question = self._question() +# +# 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 + + # INFO: "CASE" is supported by all databases: + # - http://dev.mysql.com/doc/refman/5.0/en/control-flow-functions.html#operator_case + # - http://www.sqlite.org/lang_expr.html ("The CASE expression") + # - http://www.postgresql.org/docs/8.2/static/functions-conditional.html + # But the problem is that `extra_last_updated_at` is returned as a string which can differ depedning on the backend, + # version etc. so for now let's do it manually +# posts = self.posts.extra(select={ +# 'extra_last_updated_at': 'CASE WHEN added_at > last_edited_at THEN added_at ELSE last_edited_at END', +# 'extra_last_updated_by_id': 'CASE WHEN added_at > last_edited_at THEN author_id ELSE last_edited_by_id END', +# }).order_by('-extra_last_updated_at') + + posts = self.posts.all() + + last_updated_at = posts[0].added_at + last_updated_by = posts[0].author + + for post in posts: + last_updated_at, last_updated_by = max((last_updated_at, last_updated_by), (post.added_at, post.author)) + if post.last_edited_at: + last_updated_at, last_updated_by = max((last_updated_at, last_updated_by), (post.last_edited_at, post.last_edited_by)) + + return last_updated_at, last_updated_by class QuestionQuerySet(models.query.QuerySet): diff --git a/askbot/skins/default/templates/user_profile/user_stats.html b/askbot/skins/default/templates/user_profile/user_stats.html index 6e6d2008..0691dbad 100644 --- a/askbot/skins/default/templates/user_profile/user_stats.html +++ b/askbot/skins/default/templates/user_profile/user_stats.html @@ -19,7 +19,7 @@ {% for top_answer in top_answers %}
+ href="{% url question top_answer.question_id %}{{ top_answer.thread.title|slugify }}#{{ top_answer.self_answer_id }}"> {{ top_answer.score }} @@ -27,7 +27,7 @@