diff options
author | Tomasz Zielinski <tomasz.zielinski@pyconsultant.eu> | 2011-12-10 18:38:50 +0100 |
---|---|---|
committer | Tomasz Zielinski <tomasz.zielinski@pyconsultant.eu> | 2011-12-10 18:38:50 +0100 |
commit | 0091a515009d227e897f8c2aa075940f112f9b9f (patch) | |
tree | d2f2a95392b6569b02886d151f45d0b2ed3ba78f | |
parent | b159c234d18c9c1cd4236f5ef37ad89cd284e480 (diff) | |
download | askbot-0091a515009d227e897f8c2aa075940f112f9b9f.tar.gz askbot-0091a515009d227e897f8c2aa075940f112f9b9f.tar.bz2 askbot-0091a515009d227e897f8c2aa075940f112f9b9f.zip |
Tickets 104, 107: User_stats finalization
-rw-r--r-- | askbot/models/content.py | 2 | ||||
-rw-r--r-- | askbot/models/post.py | 22 | ||||
-rw-r--r-- | askbot/models/post_view.sql | 2 | ||||
-rw-r--r-- | askbot/skins/default/templates/user_profile/user_stats.html | 6 | ||||
-rw-r--r-- | askbot/tests/cache_tests.py | 8 | ||||
-rw-r--r-- | askbot/tests/permission_assertion_tests.py | 5 | ||||
-rw-r--r-- | askbot/tests/utils.py | 17 | ||||
-rw-r--r-- | askbot/views/users.py | 60 |
8 files changed, 103 insertions, 19 deletions
diff --git a/askbot/models/content.py b/askbot/models/content.py index 1ec11d72..8424c8f6 100644 --- a/askbot/models/content.py +++ b/askbot/models/content.py @@ -84,7 +84,7 @@ class Content(models.Model): return self.html raise NotImplementedError - def get_absolute_url(self, no_slug = False): + def get_absolute_url(self, no_slug = False): # OVERRIDEN by Post.get_absolute_url() if self.is_answer(): return u'%(base)s%(slug)s?answer=%(id)d#answer-container-%(id)d' % \ { diff --git a/askbot/models/post.py b/askbot/models/post.py index b3e6770e..11a3f774 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -1,5 +1,7 @@ +from django.core import urlresolvers from django.db import models from django.core.exceptions import ValidationError +from django.utils.http import urlquote as django_urlquote from askbot.utils import markup from askbot.utils.html import sanitize_html @@ -20,6 +22,8 @@ class Post(content.Content): self_answer = models.ForeignKey('Answer', blank=True, null=True) self_question = models.ForeignKey('Question', blank=True, null=True) + question = property(fget=lambda self: self.self_answer.question) # to simulate Answer model + thread = models.ForeignKey('Thread') objects = PostManager() @@ -29,6 +33,24 @@ class Post(content.Content): db_table = 'askbot_post' managed = False + def get_absolute_url(self, no_slug = False): # OVERRIDE for Content.get_absolute_url() + from askbot.utils.slug import slugify + if self.is_answer(): + return u'%(base)s%(slug)s?answer=%(id)d#answer-container-%(id)d' % \ + { + 'base': urlresolvers.reverse('question', args=[self.self_answer.question_id]), + 'slug': django_urlquote(slugify(self.thread.title)), + 'id': self.self_answer_id + } + elif self.is_question(): + url = urlresolvers.reverse('question', args=[self.self_question_id]) + if no_slug == True: + return url + else: + return url + django_urlquote(self.slug) + raise NotImplementedError + + def delete(self, *args, **kwargs): # Redirect the deletion to the relevant Question or Answer instance # WARNING: This is not called for batch deletions so watch out! diff --git a/askbot/models/post_view.sql b/askbot/models/post_view.sql index a3487265..a2218d7d 100644 --- a/askbot/models/post_view.sql +++ b/askbot/models/post_view.sql @@ -6,7 +6,7 @@ Tested with: SQLite3 */ -CREATE VIEW askbot_post AS +CREATE VIEW IF NOT EXISTS askbot_post AS SELECT answer.id + 1000000 AS id, -- fake unique ID diff --git a/askbot/skins/default/templates/user_profile/user_stats.html b/askbot/skins/default/templates/user_profile/user_stats.html index f850c6ca..6e6d2008 100644 --- a/askbot/skins/default/templates/user_profile/user_stats.html +++ b/askbot/skins/default/templates/user_profile/user_stats.html @@ -112,20 +112,18 @@ <span class="tag-number">× <span class="badge-context-toggle">{{ badge_user_awards|length|intcomma }}</span> </span> -{# TODO: Uncomment&adjust after Django view can prepare a list of Post-s for each award <ul id="badge-context-{{ badge.id }}" class="badge-context-list" style="display:none"> {% for award in badge_user_awards %} - {% if award.content_type in (question_type, answer_type) %} + {% if award.content_object %} <li> <a title="{{ award.content_object.get_snippet()|collapse }}" href="{{ award.content_object.get_absolute_url() }}" - >{% if award.content_type == answer_type %}{% trans %}Answer to:{% endtrans %}{% endif %} {{ award.content_object.thread.title }}</a> + >{% if award.content_type.post_type == 'answer' %}{% trans %}Answer to:{% endtrans %}{% endif %} {{ award.content_object.thread.title }}</a> </li> {% endif %} {% endfor %} </ul> -#} {% if loop.index is divisibleby 3 %} </td></tr> <tr><td style="line-height:35px"> diff --git a/askbot/tests/cache_tests.py b/askbot/tests/cache_tests.py index 5eda8c74..a8416e99 100644 --- a/askbot/tests/cache_tests.py +++ b/askbot/tests/cache_tests.py @@ -3,12 +3,12 @@ from django.core.urlresolvers import reverse from django.conf import settings from askbot.tests.utils import AskbotTestCase + class CacheTests(AskbotTestCase): def setUp(self): - self.create_user() - self.create_user('other_user') - self.question = self.post_question() - self.post_answer(question = self.question) + user = self.create_user('other_user') + self.question = self.post_question(user=user) + self.post_answer(user=user, question=self.question) settings.DEBUG = True # because it's forsed to False def visit_question(self): diff --git a/askbot/tests/permission_assertion_tests.py b/askbot/tests/permission_assertion_tests.py index b83d7827..8d98cbdd 100644 --- a/askbot/tests/permission_assertion_tests.py +++ b/askbot/tests/permission_assertion_tests.py @@ -8,10 +8,10 @@ from askbot.tests import utils from askbot.conf import settings as askbot_settings from askbot import models from askbot.templatetags import extra_filters as template_filters -from askbot.tests.utils import skipIf +from askbot.tests.utils import skipIf, AskbotTestCase -class PermissionAssertionTestCase(TestCase): +class PermissionAssertionTestCase(AskbotTestCase): """base TestCase class for permission assertion tests @@ -484,6 +484,7 @@ class ReopenQuestionPermissionAssertionTests(utils.AskbotTestCase): class EditQuestionPermissionAssertionTests(utils.AskbotTestCase): def setUp(self): + super(EditQuestionPermissionAssertionTests, self).setUp() self.create_user() self.create_user(username = 'other_user') self.post = self.post_question() diff --git a/askbot/tests/utils.py b/askbot/tests/utils.py index 54a2a0ec..be2551e4 100644 --- a/askbot/tests/utils.py +++ b/askbot/tests/utils.py @@ -62,6 +62,23 @@ class AskbotTestCase(TestCase): to django TestCase class """ + def _fixture_setup(self): + # HACK: Create askbot_post database VIEW for the purpose of performing tests + import os.path + from django.conf import settings + from django.db import connection + sql = open(os.path.join(settings.PROJECT_ROOT, 'askbot', 'models', 'post_view.sql'), 'rt').read() + cursor = connection.cursor() + cursor.execute(sql) + super(AskbotTestCase, self)._fixture_setup() + + def _fixture_teardown(self): + super(AskbotTestCase, self)._fixture_teardown() + from django.db import connection + cursor = connection.cursor() + cursor.execute('DROP VIEW IF EXISTS askbot_post') + + def create_user( self, username = 'user', diff --git a/askbot/views/users.py b/askbot/views/users.py index 0d90105d..93ddce92 100644 --- a/askbot/views/users.py +++ b/askbot/views/users.py @@ -10,9 +10,8 @@ import calendar import functools import datetime import logging -import operator -from django.db.models import Count, Sum +from django.db.models import Count, Q from django.conf import settings as django_settings from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator, EmptyPage, InvalidPage @@ -282,6 +281,9 @@ def user_stats(request, user, context): if request.user != user: question_filter['is_anonymous'] = False + # + # Questions + # questions = user.posts.get_questions().filter(**question_filter).\ order_by('-score', '-thread__last_activity_at').\ select_related('thread', 'thread__last_activity_by')[:100] @@ -292,7 +294,9 @@ def user_stats(request, user, context): else: question_count = user.posts.get_questions().filter(**question_filter).count() - #this is meant for the questions answered by the user (or where answers were edited by him/her?) + # + # Top answers + # top_answers = user.posts.get_answers().filter( deleted=False, parent__deleted=False, @@ -300,32 +304,74 @@ def user_stats(request, user, context): top_answer_count = len(top_answers) + # + # Votes + # up_votes = models.Vote.objects.get_up_vote_count_from_user(user) down_votes = models.Vote.objects.get_down_vote_count_from_user(user) votes_today = models.Vote.objects.get_votes_count_today_from_user(user) votes_total = askbot_settings.MAX_VOTES_PER_USER_PER_DAY + # + # Tags + # # INFO: There's bug in Django that makes the following query kind of broken (GROUP BY clause is problematic): # http://stackoverflow.com/questions/7973461/django-aggregation-does-excessive-group-by-clauses # Fortunately it looks to return correct results for the test data user_tags = models.Tag.objects.filter(threads__post__author=user).\ annotate(user_tag_usage_count=Count('threads')).\ order_by('-user_tag_usage_count')[:const.USER_VIEW_DATA_SIZE] + user_tags = list(user_tags) # evaluate + + + # + # Badges/Awards (TODO: refactor into Managers/QuerySets when a pattern emerges; Simplify when we get rid of Queastion&Answer models) + # + question_type = ContentType.objects.get_for_model(models.Question) + answer_type = ContentType.objects.get_for_model(models.Answer) user_awards = models.Award.objects.filter(user=user).select_related('badge') + + awarded_answer_ids = [] + awarded_question_ids = [] + for award in user_awards: + if award.content_type_id == question_type.id: + awarded_question_ids.append(award.object_id) + elif award.content_type_id == answer_type.id: + awarded_answer_ids.append(award.object_id) + + awarded_posts = models.Post.objects.filter( + Q(self_answer__in=awarded_answer_ids)|Q(self_question__in=awarded_question_ids) + ).select_related('self_answer', 'thread') # select related to avoid additional queries in Post.get_absolute_url() + awarded_questions_map = {} + awarded_answers_map = {} + for post in awarded_posts: + if post.self_question_id: + awarded_questions_map[post.self_question_id] = post + elif post.self_answer_id: + awarded_answers_map[post.self_answer_id] = post + badges_dict = {} + for award in user_awards: + # Fetch content object + if award.content_type_id == question_type.id: + award.content_object = awarded_questions_map[award.object_id] + elif award.content_type_id == answer_type.id: + award.content_object = awarded_answers_map[award.object_id] + else: + award.content_object = None + + # "Assign" to its Badge if award not in badges_dict: badges_dict[award.badge] = [award] else: badges_dict[award.badge].append(award) + badges = list(badges_dict.items()) badges.sort(key=lambda badge_tuple: len(badge_tuple[1]), reverse=True) - # TODO: fetch award.content_object in one query, as a list of Post-s, and pass to template to avoid subsequent queries there - total_badges = len(badges) -# INFO: Shorter version for fetching badges, but then it's hard to do the postprocessing (i.e. getting user awards per badge etc.) -# badges = models.BadgeData.objects.filter(award_badge__user=user).annotate(user_awarded_times=Count('award_badge')) + total_badges = len(badges) data = { 'active_tab':'users', |